1 Introducción

En el campo del análisis de señales, uno de los problemas más relevantes es la eliminación de ruido en imágenes digitales. Las señales, que representan variaciones de magnitudes en el espacio y/o tiempo, a menudo están contaminadas por ruido debido a interferencias en los procesos de captura o transmisión. Este ruido, que puede existir de formas muy diversas como ruido gaussiano, artefactos asociados a frecuencias altas o bajas, o efectos multiplicativos, complica la extracción de características significativas.

El uso de wavelets es una herramienta clave para abordar este problema, el cual permite descomponer señales en sus componentes espaciales y frecuenciales de forma eficiente. Este método ofrece la posibilidad de adaptarse a las características particulares de cada tipo de ruido.

En este trabajo se propone explorar la capacidad de los wavelets para la eliminación de ruido en imágenes digitales. Para ello, se generarán y analizarán diferentes tipos de ruido artificial, evaluando su complejidad para su atenuación y determinando los parámetros más eficaces de los wavelets.

2 Fundamento teórico: eliminación de ruido con wavelets

Los wavelets son funciones matemáticas que permiten descomponer una señal en componentes de diferentes escalas, lo que resulta útil para identificar y procesar características específicas. Este enfoque es especialmente relevante en la eliminación de ruido, donde las frecuencias indeseadas pueden ser separadas y reducidas sin afectar significativamente las características principales de la señal original. A diferencia de la Transformada de Fourier, que opera globalmente y no ofrece información sobre la localización temporal de los eventos, los wavelets permiten un análisis localizado, facilitando la identificación de patrones y anomalías en los datos.

El proceso de reducción de ruido con wavelets generalmente incluye tres etapas principales: la descomposición de la señal utilizando una wavelet madre, la modificación de los coeficientes wavelet mediante técnicas de umbral, y la reconstrucción de la señal. La selección de la wavelet madre adecuada y los parámetros de umbral son aspectos críticos que deben adaptarse a las características específicas del ruido y la señal.

Además, los wavelets ofrecen un enfoque multi-resolución, permitiendo una representación detallada de los componentes de alta frecuencia, asociados frecuentemente con el ruido, mientras preservan las estructuras globales de baja frecuencia. Esta característica hace que los wavelets sean especialmente útiles en aplicaciones donde la precisión y la integridad de los datos son esenciales, como en imágenes médicas, procesamiento de audio o análisis de datos científicos.

En este trabajo, se emplearán wavelets como herramienta principal para eliminar diferentes tipos de ruido sintético en imágenes.

3 Funciones de programacion empleadas

Antes de comenzar, instalamos y/o cargamos todos los paquetes requeridos.

rm(list = ls())
if (!require("pacman")) install.packages("pacman")
Cargando paquete requerido: pacman
library(pacman)
p_load(imager, wavethresh, ggplot2, dplyr, SpatialPack, waveslim, EBImage, stringr, jpeg, abind, magick, knitr)

Se ha escogido emplear tres métodologías distintas para realizar la eliminación de ruido. Dos de ellas se basan en emplear la transformada wavelet. Sin embargo, también se ha optado por emplear en un tercer método el análisis mediante transformadas de Fourier con el objetivo de encontrar similitudes y diferencias con respecto a los métodos que emplean wavelets.

Con respecto a los métodos con wavelts utilizaremos un método muy directo, empleando la función denoise.dwt.2d del paquete waveslim de R. Por otro lado, emplearemos un método más manual que consiste en realizar la transformada wavelet discreta con la función imwd, aplicar una función para realizar el thresholding y a continuación realizar la transformada inversa.

Para finalizar, se realizará la eliminación de ruido a través de transformadas de Fourier con la función fftshift y compararemos los resultados obtenidos con cada método.

Método 1. Eliminación de ruido con el algoritmo de Mallat

Empleamos la funicón imwd() del paquete wavethresh. Está función realiza una transformada discreta wavelet de acuerdo con el algoritmo de Mallat.

El argumento image de la función debe ser una matriz cuadrada cuya dimensión sea potencia de dos.filter.number elige la suavidad de la wavelet a emplear, siendo por defecto 10. family indica la familia de wavelets a emplear (“DaubExPhase” ó “DaubLeAsymm”).Para tratar las fronteras, mantendremos el parámetro bc = "periodic" por defecto.

# Representación de las dos familias de wavelets empleadas por la función imwd.

# Generación de un filtro con los coeficientes correspondientes de cada wavelet
wave_coeffs <- filter.select(filter.number = 10, family = "DaubLeAsymm")
wave_coeffs_2 <- filter.select(filter.number = 10, family = "DaubExPhase")

# Eje temporal
x <- seq_along(wave_coeffs$H)

# # Visualización de las wavelets
par(mfrow = c(1, 2))

plot(x, wave_coeffs$H, type = "l", lwd = 2,
     main = "Wavelet DaubLeAsymm",
     xlab = "x", ylab = "Amplitud")
points(x, wave_coeffs$H, pch = 19)

plot(x, wave_coeffs_2$H, type = "l", lwd = 2,
     main = "Wavelet DaubExPhase",
     xlab = "x", ylab = "Amplitud")
points(x, wave_coeffs_2$H, pch = 19)



rm(wave_coeffs)
rm(wave_coeffs_2)
rm(x)

Para la eliminación de ruido aplicaremos la función threshold() al objeto que devuelve la función imwd().

levels es el número de niveles a los cuáles deseamos aplicar un umbral, mientras que type indica si queremos un umbral más suave (“soft”) ó más fuerte (“hard”). El parámetro policy selecciona la técnica para elegir umbral. by.level = FALSE significa que se aplica un umbral global a todos los niveles indicados, mientras que by.level = TRUE calcular threshold para cada nivel por seaparado. El parámetro value, el valor del umbral, se usará si elegimos técnicas manuales en policy. Si queremos obtener el valor umbral aplicado, pondremos return.threshold = TRUE. Finalmente, dejaremos el parámetro compression = TRUE por defecto, para obterner un objeto más pequeño.

Durante el desarrollo del trabajo variaremos estos parámetros para observar su efecto en la eliminación de diversos tipos de ruido y encontar aquellos que generen un mejor resultado en cada caso. Una vez realizado el thresholding, emplearemos la función imwr para realizar la transformda wavelet inversa y poder visualizar la imagen tras la eliminación de ruido.

Método 2. Función denoise.dwt.2d

Empleamos la función denoise.dwt.2d del paquete waveslim para eliminar el ruido de las imágenes empleando la transformada wavelet discreta (DWT).

Esta función descompone la imagen en coeficientes wavelet, aplicando un filtro wavelet seleccionado (wf) en ambos ejes (DWT-2D). Como resultado, la imagen queda dividida en 4 cuadrantes:

Esta descomposición en 4 cuadrantes se repite tantas veces como niveles (J) se especifiquen en la función.

El siguiente paso es aplicar un umbral para ajustar o eliminar los coeficientes de más alta frecuencia en los de cuadrantes de detalle HL, LH y HH en cada nivel de descomposición. Cabe destacar que no se aplica el umbral al cuadrante LL, ya que contiene las componentes de baja frecuencia, que generalmente no están afectadas por el ruido.

Después de aplicar el umbral para suprimir el ruido, la imagen se reconstruye empleando la transformada wavelet inversa (IDWT). Como se observa, esta función es una aplicación más directa del método anterior, ya implementada en una única función.

Para explorar las posibilidades de esta función, realizamos las siguientes pruebas:

Emplearemos los 6 tipos de ruido que hemos definido anteriormente en las imágenes de la 1 a la 4 (las imagenes 1 y 2 las emplearemos 2 veces). Emplearemos el ruido Gaussiano en la imagen 5 que presenta menor resolución.

Probaremos tres niveles de descomposición distintos. El ruido suele encontrarse en las altas frecuencias de los niveles más bajos, mientras que en los niveles altos se encuentran los detalles más finos. Esperamos que un mayor nivel de descomposición genere imágenes más suavizadas, pero con menos detalles.

Finalmente, realizaremos la descomposición de la imagen con 4 filtros wavelet distintos, son 4 wavelets con diferentes formas (pueden ser simétricas o no), y diferente numero de coeficientes.

Método 3. Transformada de Fourier (función fftshift)

(Borrador)

El método usado usa la transformada rápida de fourier para poder eliminar altas frecuencias correspondientes a ruido en la imagen. La función fftshift desplaza las bajas frecuencias al centro del espectro y las altas a los extremos. Una vez se visualiza el espectro, se procede a disminuir la magnitud de las altas frecuencias cercanas a los extremos asignandoles una magnitud menor. Con este procedimiento estamos aplicando un filtro de paso bajo y aplicando ifftshift obtenemos la imagen en el dominio espacial con ruido eliminado.

Además, a lo largo del trabajo se programar diversas funciones que ayudarán a autimatizar el proceso.

4 Desarrollo y resultados

Comenzamos cargando y visualizando las fotografías a emplear.

images_path <- list.files("./fotos", full.names = TRUE)

nombres_images <- str_remove_all(string = str_remove_all(string = images_path, pattern = "./fotos/"), pattern = "\\.JPG|\\.jpg")

images <- lapply(images_path, readJPEG) # Cargamos las imágenes
names(images) <- nombres_images

rm(images_path)
# Rotamos algunas de las fotos para una visualización más uniforme
fotos_a_girar <- c("1", "2", "3", "4")
images_rotadas <- lapply(images[fotos_a_girar], aperm, perm = c(2, 1, 3))


for (i in fotos_a_girar) {
  images_rotadas[[i]] < images_rotadas[[i]][dim(images_rotadas[[i]])[1]:1, , ]
}

images[fotos_a_girar] <- images_rotadas
rm(images_rotadas)
rm(fotos_a_girar)

Vamos a visualizar las imágenes empleadas. Se han tomado 5 imágenes distintas pero todas con la misma temática. La elección se ha realizado pensando en poder observar como afecta la elimación de ruido detalles como la textura o defectos en las frutas. Además, las imágenes 1 a 4 se han tomado con una cámara de gran calidad, mientras que la imagen 5 cuenta con una resolución mucho menor. Se busca también estudiar que efecto tiene la resolución de la imagen y su calidada en la eliminación de ruido.

# Visualizamos las imagenes originales 
par(mfrow = c(2, 3), mar = c(1, 1, 1, 1))

 for (img in nombres_images) {
   display(Image(images[[img]], colormode = "Color"), method = "r")
   title(paste('Imagen', img))
 }

4.1 Inclusión de ruido sintético en las imágenes

Se crean la función add_noise_to_image y NOISE_TYPES para añadir el ruido a las imágenes. Los ruidos que se han generado, con parámetros ajustables a variar, son los siguientes:

  • Ruido gaussiano de media 0 y desviación típica ajustable.
  • Ruidos sinusoidales de alta y baja frecuencia.
  • Ruido sal y pimienta: Este tipo de ruido sustituye píxeles aleatorios en la imagen por valores mínimos (negros) o máximos (blancos), simulando un patrón de puntos oscuros y brillantes
  • Ruido gamma multiplicativo: multiplica el valor de los píxeles por una distribución gamma.
  • Ruido uniforme multiplicativo: multiplica el valor de los píxeles por una distribución uniforme.

Estos ruidos sintéticos pueden imitar ruidos que se encuentren en imágenes reales. Por ejemplo, el ruido sinusoidal introduce un patrón periódico de oscilaciones que pueden simular interferencias periódicas. Otro ejemplo es el ruido sal y pimienta, que puede simular fallos en la captura de las imágenes (la aparición de puntos blancos y negros).

# Definición de tipos de ruido

NOISE_TYPES <- list(
  gaussian = list(
    generator = function(channel, params) {
      # Desviación estándar del ruido con un valor predeterminado
      noise_std_dev <- params$std_dev %||% 0.5

      # Generación de ruido gaussiano
      noise <- array(
        rnorm(length(channel), mean = 0, sd = noise_std_dev),
        dim = dim(channel)
      )

      # Asegurar que los valores estén entre 0 y 1
      pmax(0, pmin(1, channel + noise))
    }
  ),
  sinusoidal_high = list(
    generator = function(channel, params) {
      # Frecuencia y amplitud del ruido sinusoidal de alta frecuencia
      frequency <- params$frequency %||% 25
      amplitude <- params$amplitude %||% 0.2

      # Generación de ruido sinusoidal
      height <- dim(channel)[1]
      width <- dim(channel)[2]
      x <- seq(0, 2 * pi, length.out = width)
      y <- seq(0, 2 * pi, length.out = height)
      noise_grid <- outer(sin(x * frequency), sin(y * frequency))

      # Aplicar el ruido
      noise <- array(noise_grid * amplitude, dim = dim(channel))
      pmax(0, pmin(1, channel + noise))
    }
  ),
  sinusoidal_low = list(
    generator = function(channel, params) {
      # Frecuencia y amplitud del ruido sinusoidal de baja frecuencia
      frequency <- params$frequency %||% 2
      amplitude <- params$amplitude %||% 0.2

      # Generación de ruido sinusoidal
      height <- dim(channel)[1]
      width <- dim(channel)[2]
      x <- seq(0, 2 * pi, length.out = width)
      y <- seq(0, 2 * pi, length.out = height)
      noise_grid <- outer(sin(x * frequency), sin(y * frequency))

      # Aplicar el ruido
      noise <- array(noise_grid * amplitude, dim = dim(channel))
      pmax(0, pmin(1, channel + noise))
    }
  ),
  salt_pepper = list(
    generator = function(channel, params) {
      # Proporción de píxeles afectados por el ruido de sal y pimienta
      epsilon <- params$epsilon %||% 0.2

      # Generación de ruido
      noise <- matrix(sample(c(0, 1, NA), length(channel), replace = TRUE, prob = c(epsilon / 2, epsilon / 2, 1 - epsilon)),
        nrow = dim(channel)[1], ncol = dim(channel)[2]
      )
      channel[!is.na(noise)] <- noise[!is.na(noise)]
      channel
    }
  ),
  gamma = list(
    generator = function(channel, params) {
      # Ruido multiplicativo gamma con parámetro de dispersión
      looks <- params$looks %||% 2
      noise <- array(rgamma(length(channel), shape = looks, scale = 1 / looks), dim = dim(channel))
      pmax(0, pmin(1, channel * noise))
    }
  ),
  uniform_multiplicative = list(
    generator = function(channel, params) {
      # Ruido multiplicativo uniforme
      looks <- params$looks %||% 2
      noise_channel <- SpatialPack::imnoise(
        img = channel,
        type = "speckle",
        looks = looks
      )
      pmax(0, pmin(1, noise_channel))
    }
  )
)
# Función para añadir ruido a una imagen
add_noise_to_image <- function(image_name, noise_type, noise_params = list(), plot = FALSE) {
  # Verificar si la imagen existe en la lista
  if (!image_name %in% names(images)) {
    stop("La imagen con este nombre no se encuentra en la lista 'images'")
  }

  # Verificar el tipo de ruido
  if (!noise_type %in% names(NOISE_TYPES)) {
    stop(
      "El tipo de ruido es desconocido. Tipos disponibles: ",
      paste(names(NOISE_TYPES), collapse = ", ")
    )
  }

  # Obtener la imagen original de la lista
  original_image <- images[[image_name]]

  # Convertir la imagen a un array si es necesario
  image_array <- as.array(original_image)

  # Aplicar ruido a cada canal
  noisy_channels <- lapply(1:3, function(i) {
    channel <- image_array[, , i]
    NOISE_TYPES[[noise_type]]$generator(channel, noise_params)
  })

  # Crear la imagen con ruido
  noisy_image_array <- array(
    unlist(noisy_channels),
    dim = dim(image_array)
  )


  # Visualizar si se ha indicado
  if (plot == TRUE){
  layout(matrix(1:2, 1, 2))
  plot(Image(original_image, colormode = "Color"))
  title("Original")
  plot(Image((noisy_image_array), colormode = "Color"))
  title(paste("Ruido:", noise_type))}
  
  return(noisy_image_array)
}


# Aplicar los diferentes tipos de ruido a cada imagen

# add_noise_to_image("1", "gaussian", list(std_dev = 0.3))
# add_noise_to_image("2", "sinusoidal_high", list(frequency = 25, amplitude = 0.2))
# add_noise_to_image("3", "sinusoidal_low", list(frequency = 2, amplitude = 0.2))
# add_noise_to_image("4", "salt_pepper", list(epsilon = 0.1))
# add_noise_to_image("5", "gamma", list(looks = 2))
# add_noise_to_image("5", "uniform_multiplicative", list(looks = 2))

4.2 Función imwd

El uso de la Transformada Discreta de Wavelet (DWT) permite separar las frecuencias bajas de las frecuencias altas (que son las que suelen contener el ruido).

Se aplica la DWT en una imagen \(f(x, y)\) de tamaño \(M \times N\) con filtros paso-bajo y paso-alto en cada dimensión:

  • \(H_1\) y \(H_2\) son los filtros paso-bajo y paso-alto en la dirección horizontal, respectivamente.
  • \(V_1\) y \(V_2\) son los filtros paso-bajo y paso-alto en la dirección vertical.

La DWT se descompone en cuatro sub-imágenes:

  1. Aproximación (LL): Resultado de aplicar los filtros \(H_1\) y \(V_1\). Contiene las bajas frecuencias de la imagen, es decir, la versión suavizada de la imagen.

  2. Horizontal (LH): Resultado de aplicar \(H_1\) y \(V_2\). Captura detalles horizontales, como bordes verticales.

  3. Vertical (HL): Resultado de aplicar \(H_2\) y \(V_1\). Captura detalles verticales, como bordes horizontales.

  4. Diagonal (HH): Resultado de aplicar \(H_2\) y \(V_2\). Captura detalles diagonales, como bordes inclinados y ruido.

La DWT 2D se expresa matemáticamente como:

\[ f_{LL}(x, y) = H_1(f(x, y)) * V_1(f(x, y)) \]

\[ f_{LH}(x, y) = H_1(f(x, y)) * V_2(f(x, y)) \]

\[ f_{HL}(x, y) = H_2(f(x, y)) * V_1(f(x, y)) \]

\[ f_{HH}(x, y) = H_2(f(x, y)) * V_2(f(x, y)) \]

Umbral Universal

El umbral “universal”, propuesta por Donoho y Johnstone. Esta estrategia calcula el umbral aplicado a los coeficientes de wavelet en función del tamaño de la señal y una estimación del nivel de ruido. Este enfoque tiene como objetivo establecer un umbral de manera que se eliminen los coeficientes de wavelet que corresponden al ruido, mientras se conservan aquellos que contienen la señal significativa. La fórmula del umbral “universal” es \[ \sigma \sqrt{2 \log nd}\] donde \(\sigma\) es una estimación del ruido y nd es el número de coeficientes en la subbanda de detalles correspondiente a un nivel de la transformada wavelet. Este valor se obtiene accediendo a los coeficientes de la subbanda D de cada nivel.

Umbral FDR

La tasa de falsos positivos (FDR) es una técnica estadística utilizada para controlar la tasa de falsos positivos en el proceso de selección de coeficientes relevantes, tal como se describe en el trabajo de Abramovich y Benjamini (1996). En el contexto de la reducción de ruido mediante la Transformada Wavelet, el objetivo principal de FDR es identificar y eliminar los coeficientes asociados al ruido, mientras se preservan aquellos que contienen información significativa, como bordes, texturas o detalles importantes de la imagen. Esto se logra calculando, para cada coeficiente de la transformada, la probabilidad de que dicho coeficiente sea un falso positivo, es decir, que corresponda a ruido pero sea erróneamente considerado relevante.

Metodología

Una vez estimado el nivel de ruido, se calcula para cada coeficiente de wavelet la probabilidad \(p\) de que ese coeficiente sea ruido, utilizando la fórmula:

\[ p = 2 \left( 1 - \Phi \left( \frac{|d|}{\text{noise.level}} \right) \right) \]

donde:

\(|d|\) es el valor absoluto del coeficiente \(d\) de la subbanda de detalles.

\(\text{noise.level}\) es la desviación estándar de los coeficientes de esa subbanda.

\(\Phi\) es la función de distribución acumulada de la normal estándar, que nos da la probabilidad de que un valor \(d\) dado provenga del ruido (asumido como una distribución normal).

Umbralización según FDR

El valor calculado de \(p\) da una medida de la probabilidad de que un coeficiente sea ruido. A continuación, se define un umbral para la tasa de descubrimientos falsos, denotada por \(Q\), que establece el límite máximo aceptable para la probabilidad de falso positivo. Con un \(Q = 0.05\), esto significa que se permitirá un máximo del 5% de coeficientes de ruido que se consideren erróneamente relevantes.

Los coeficientes cuya probabilidad \(p\) sea mayor que el umbral calculado se eliminan (se consideran ruido y se establecen a cero), mientras que aquellos con \(p\) menor que el umbral se conservan, ya que se consideran significativos (es decir, pertenecen a la señal de la imagen).

Para aplicar el algoritmo de Mallat (IMWD), es necesario que la imagen tenga una forma cuadrada cuyas dimensiones sean potencia de dos. Dado que muchas imágenes no son cuadradas, es necesario convertirlas antes de aplicar el algoritmo, por lo que debemos realizar un pre-procesamiento de las imágenes. Se han escogido 2 maneras distintas para obtener imágenes con el tamaño adecuado. Por un lado, redimensionaremos las imágenes con la función resize, lo que podría conllevar problemas de distorsión si las imágenes estaban lejos de tener dimensiones cuadradas. Por ello, también vamos a emplear otra técnica y rellenaremos las matrices de las imágenes con valores de 0 ó 1 hasta alcanzar las dimensiones adecuadas. Observamos los problemas que surguen en cada caso y trataremos de solucionarlos de distintas maneras.

4.2.1 Redimensionando las imágenes

Como ya se ha visto para poder aplicar la función imwd()es necesario partir de una matriz cuadrada cuyas dimensiones sean potencia de dos. Por ello, en primer lugar creamos una función resize_imwd() tal que dada una foto busca la submatriz cuadrada y potencia de dos más grande posible y a continuacón redimensiona la imagen a dicha submatriz cuadrada.

# Pre-procesamiento al aplicado de función imwd (algoritmo de Mallat).
# Redimensionamiento de la imagen 

resize_imwd<- function(foto){
  
  img <- as.cimg(foto) # Pasar a formato Imager para aplicar función resize
   
  # Dimensiones de la foto
  dim_foto <- dim(foto)
  filas <- dim_foto[1]
  columnas <- dim_foto[2]
  
  lado_minimo <- min(filas, columnas)  # Tamaño submatriz cuadrada mas grande
  lado_potencia2 <- 2^floor(log2(lado_minimo)) # Tamaño submatriz cuadrada potencia de dos mas grande
  
  foto_resized <-resize(foto, w = lado_potencia2, h = lado_potencia2) # Redimensionado de la imagen

return(foto_resized)
}

Por otro lado, creamos una función para el post-procesamiento de las imágenes tras la eliminición de ruido. Queremos devolverlas a su tamaño original con el objetivo de comparar con las imágenes iniciales.

# Post-procesamiento de la imagen:
# Redimensionamiento de la imagen a su tamaño original.

resize_imwd_to_original<- function(image_redimensionada, nombre_foto){
  
  #img <- as.cimg(image_redimensionada)
  foto <- images[[nombre_foto]]
  
  # Dimensiones de la foto
  dim_foto <- dim(foto)
  filas <- dim_foto[2]
  columnas <- dim_foto[1]
  
  # Redimensionado de la imagen
  foto_resized <-EBImage::resize(image_redimensionada, w = columnas, h = filas) 
 

return(foto_resized)
}

Comenzamos generando una función procesar_imagen_wavelet con parámetros foto, tipo y policy. Esta función realiza en primer lugar la transformada wavelet a cada uno de los tres canales de una imagen. A continuación, se realiza el thresholding con la función threshold, pudiendo variar de el tipo de “hard” a “soft” y el parámetro policy (modificando adecuadamente los parámetros necesarios en la función threshold en este último caso). Una vez realizada la eliminación de ruido, se aplica la trasnformada wavelet inversa imwr para por último reconstruir la imagen a partir de los tres canales.

procesar_imagen_wavelet <- function(foto, tipo = "hard", policy = "universal") {
  # 1. Realizamos la transformada wavelet a cada canal
  lwd <- lapply(1:3, function(canal) {
    imwd(foto[,,canal])  
  })
  
  # 2. Aplicamos el umbral a los coeficientes de la transformada wavelet
  lwd_threshold <- lapply(lwd, function(canal_wd) {
    niveles <- canal_wd$nlevels
    wavethresh::threshold(canal_wd, levels = 3:(niveles-1), type = tipo, policy = policy,by_level=TRUE,compression=FALSE)
  })
  # 3. Aplicamos la transformada wavelet inversa a cada canal umbralizado
  ilwd <- lapply(lwd_threshold, function(canal_umbralizado) {
    wavethresh::imwr(canal_umbralizado)  # Transformada wavelet inversa
  })
  
  # 4. Reconstruir la imagen combinando los tres canales procesados
  imagen_reconstruida <- abind::abind(ilwd[[1]], ilwd[[2]], ilwd[[3]], along = 3)
    imagen <- Image(imagen_reconstruida, colormode = 'Color')
  
  return(imagen)
}

Para comenzar el análisis, vamos a emplear dos imágenes muy parecidas (imágenes 4 y 5). Una de ellas tiene muy alta resolución mientras que la segunda cuenta con una calidad mucho menor. El objetivo es determinar si la resolución de la imagen afecta a la hora de eliminar ruido de esta. Vamos a probar con el primer tipo de ruido, ruido gaussiano.

# Añadimos ruido gaussiano a las imágenes con sd = 0.3
image_4_gaussian_noise <- add_noise_to_image("4", "gaussian", list(std_dev = 0.3))
image_5_gaussian_noise <- add_noise_to_image("5", "gaussian", list(std_dev = 0.3))

# Hacemos una lista con las imágenes con ruido a redimensionar
images_for_imwd_cut_1 <- list(image_4_gaussian_noise, image_5_gaussian_noise)
names(images_for_imwd_cut_1) <- c('4 Noise: gaussian', '5 Noise: gaussian')

# Eliminamos variables innecesarias
rm(image_4_gaussian_noise)
rm(image_5_gaussian_noise)

Aplicamos la función resize_imwd creada anteriormente para obtener una matriz con las dimensiones necesarias para aplicar la transformada wavelet.

images_recortadas <- lapply(images_for_imwd_cut_1, resize_imwd)

Una vez tenemos las imágenes con ruido generadas y redimensionadas adecuadamente, podemos aplicar la función imwd a cada uno de los tres canales (R, G y B). Empleamos la función procesar_imagen_wavelet que devuelve las imágenes reconstruidas después del thresholding. Para ruido gaussiano y para comenzar, vamos a elegir los parámetros por defecto de la función para el thresholding.

images_sin_ruido_gaussiano <- lapply(images_recortadas, procesar_imagen_wavelet)

Finalmente, visualizamos los resultados. Primeramente, observamos las imágenes redimensionadas con ruido y la imagen obtenida tras el uso del método de thresholding para la eliminación de este. Observamos una principal diferencia entre ambas: la foto que contaba con menor resolución presenta también el peor resultado. Aunque el ruido haya sido eliminadom, sus bordes están más difuminados y tiene muy baja calidad.

par(mfrow = c(2, 2), cex = 0.5)

display(Image(images_recortadas[[1]], colormode = 'Color'), method='r')
title('Imagen 4 con ruido')
display(Image(images_sin_ruido_gaussiano[[1]], colormode = 'Color'), method='r')
title('Imagen 4 sin ruido')

display(Image(images_recortadas[[2]], colormode = 'Color'), method='r')
title('Imagen 5 con ruido')
display(Image(images_sin_ruido_gaussiano[[2]], colormode = 'Color'), method='r')
title('Imagen 5 sin ruido')

En segundo lugar, vamos a visualizar las imágenes originales y las imágenes sin ruido redimensionadas a su tamaño original, usando la función resize_imwd_to_original.

images_sin_ruido <- Map(resize_imwd_to_original, images_sin_ruido_gaussiano, c("4", "5") )
par(mfrow = c(2, 2), cex = 0.5)

display(Image(images[[4]], colormode = 'Color'), method='r')
title('Imagen 4 original')
display(Image(images_sin_ruido[[1]], colormode = 'Color'), method='r')
title('Imagen 4 sin ruido')

display(Image(images[[5]], colormode = 'Color'), method='r')
title('Imagen 5 original')
display(Image(images_sin_ruido[[2]], colormode = 'Color'), method='r')
title('Imagen 5 sin ruido')

Vemos que efectivamente, la imagen que originalemente contaba con un número de píxeles mucho menor, la imagen 5, presenta una gran distorsión tras la eliminación de ruido.

rm(images_for_imwd_cut_1,images_sin_ruido_gaussiano)

A continuación vamos a comprobar que es lo que ocurre cuando añadimos ruido sintético sinusoidal y si la frecuencia de este afecta al resultado de la eliminación de ruido. Trabajaremos con una única fotografía: la imagen 1.

# Añadimos ruido sinusoidal a las imágenes con sd = 0.3
image_1_sinosuidal_high <- add_noise_to_image("1", "sinusoidal_high", list(frequency = 50, amplitude = 0.3))
image_1_sinosuidal_low <- add_noise_to_image("2", "sinusoidal_low", list(frequency = 5, amplitude = 0.3))

# Hacemos una lista con las imágenes con ruido a redimensionar
images_for_imwd_cut_2 <- list(image_1_sinosuidal_high , image_1_sinosuidal_low )
names(images_for_imwd_cut_2) <- c('1 Noise: sinusoidal high', '1 Noise: sinusoidal low')

# Eliminamos variables innecesarias
rm(image_1_sinosuidal_high)
rm(image_1_sinosuidal_low)
images_recortadas <- lapply(images_for_imwd_cut_2, resize_imwd)
images_sin_ruido_sinusoidal<- lapply(images_recortadas, procesar_imagen_wavelet)

Esta claro que los parámetros por defecto de la función threshold no son capaces de eliminar el ruido de tipo sinusoidal de la manera en que si lo era con el ruido de tipo Gaussiano, un tipo de ruido aleatorio, al contrario que el sinusoidal, que es una señal periódica.

par(mfrow = c(2, 2), cex = 0.5)

display(Image(images_recortadas[[1]], colormode = 'Color'), method='r')
title('Imagen 1 con ruido de frecuencia alta')
display(Image(images_sin_ruido_sinusoidal[[1]], colormode = 'Color'), method='r')
title('Imagen 1 sin ruido')

display(Image(images_recortadas[[2]], colormode = 'Color'), method='r')
title('Imagen 1 con ruido de frecuencia baja')
display(Image(images_sin_ruido_sinusoidal[[2]], colormode = 'Color'), method='r')
title('Imagen 1 sin ruido')

Vamos a variar parámetros de la función threshold para intentar quitar este ruido de manera más manual. En primer lugar, cambiamos el número de niveles al que aplicamos el umbral, para incluirlos a todos. Cambiamos policy a “manual” y variamos el valor del umbral de forma manual hasta encontrar uno que sea satisfactorio.

procesar_imagen_wavelet_sinusoidal <- function(foto, tipo = "hard", policy = "universal") {
  # 1. Realizamos la transformada wavelet a cada canal
  lwd <- lapply(1:3, function(canal) {
    imwd(foto[,,canal])  
  })
  
  # 2. Aplicamos el umbral a los coeficientes de la transformada wavelet
  lwd_threshold <- lapply(lwd, function(canal_wd) {
    niveles <- canal_wd$nlevels
    wavethresh::threshold(canal_wd, levels = 1:(niveles-1), type = tipo, policy = policy,by_level=TRUE,compression=FALSE, value = 4)
  })
  # 3. Aplicamos la transformada wavelet inversa a cada canal umbralizado
  ilwd <- lapply(lwd_threshold, function(canal_umbralizado) {
    wavethresh::imwr(canal_umbralizado)  # Transformada wavelet inversa
  })
  
  # 4. Reconstruir la imagen combinando los tres canales procesados
  imagen_reconstruida <- abind::abind(ilwd[[1]], ilwd[[2]], ilwd[[3]], along = 3)
    imagen <- Image(imagen_reconstruida, colormode = 'Color')
  
  return(imagen)
}
images_sin_ruido_sinusoidal<- lapply(images_recortadas, procesar_imagen_wavelet_sinusoidal, tipo ="soft", policy = "manual")

Con un valor de umbral de 4 y variando el tipo a “soft”, observamos que hemos conseguido eliminar el ruido sinusoidal de alta frecuencia, pero pagando un precio muy alto: los bordes de la imagen se disorsionan completamente y tenemos una muy baja resolución. Por otro lado, el ruido de frecuencia baja, aunque ha disminuido, claramente sigue presente en la imagen. El umbral necesario para eliminarlo con este método es tan alto que distorsionaría la imagen casi por completo.

par(mfrow = c(2, 2), cex = 0.5)

display(Image(images_recortadas[[1]], colormode = 'Color'), method='r')
title('Imagen 1 con ruido de frecuencia alta')
display(Image(images_sin_ruido_sinusoidal[[1]], colormode = 'Color'), method='r')
title('Imagen 1 sin ruido')

display(Image(images_recortadas[[2]], colormode = 'Color'), method='r')
title('Imagen 1 con ruido de frecuencia baja')
display(Image(images_sin_ruido_sinusoidal[[2]], colormode = 'Color'), method='r')
title('Imagen 1 sin ruido')

Probamos otros tipos de ruido: gamma, salt and pepper y ruido uniforme en las imágenes 2 y 3.

# Añadimos ruido a imágenes
image_2_salt_pepper <- add_noise_to_image("2", "salt_pepper", list(epsilon = 0.1))
image_3_gamma <- add_noise_to_image("3", "gamma", list(looks = 2))
image_3_uniform <- add_noise_to_image("3", "uniform_multiplicative", list(looks = 2))


# Hacemos una lista con las imágenes con ruido a recortar
images_for_imwd_cut_3 <- list( image_2_salt_pepper, image_3_gamma, image_3_uniform)
names(images_for_imwd_cut_3) <- c('2 Noise: salt and pepper', '3 Noise: gamma','3 Noise: uniform')

# Eliminamos variables innecesarias
rm(image_2_salt_pepper, image_3_gamma, image_3_uniform)

Suguiendo el mismo procedimiento, redimensionamos las imágenes y y realizamos las transformadas wavelet y el thresholding con la función procesar_imagen_wavelet.

images_recortadas <- lapply(images_for_imwd_cut_3, resize_imwd)
images_sin_ruidos<- lapply(images_recortadas, procesar_imagen_wavelet)

Visualizamos los resultados. Parece que el algoritmo funciona correctamente para los tres tipos de ruido.

par(mfrow = c(3, 2), cex = 0.5)

display(Image(images_recortadas[[1]], colormode = 'Color'), method='r')
title('Imagen 2 con salt and pepper')
display(Image(images_sin_ruidos[[1]], colormode = 'Color'), method='r')
title('Imagen 2 sin ruido')

display(Image(images_recortadas[[2]], colormode = 'Color'), method='r')
title('Imagen 3 con ruido gamma')
display(Image(images_sin_ruidos[[2]], colormode = 'Color'), method='r')
title('Imagen 3 sin ruido')

display(Image(images_recortadas[[3]], colormode = 'Color'), method='r')
title('Imagen 3 con ruido uniforme')
display(Image(images_sin_ruidos[[3]], colormode = 'Color'), method='r')
title('Imagen 3 sin ruido')

Redimensionamos las imágenes obtenidas a su tamaño original para poder compararlas con estas. Vemos que en este caso la eliminación de ruido ha sido muy buena y no hay apenas distorsión ni suavizado de los bordes.

images_sin_ruido <- Map(resize_imwd_to_original, images_sin_ruidos, c("2", "3", "3") )
par(mfrow = c(3, 2), cex = 0.5)

display(Image(images[[2]], colormode = 'Color'), method='r')
title('Imagen 2 original')
display(Image(images_sin_ruido[[1]], colormode = 'Color'), method='r')
title('Imagen 2 sin ruido')

display(Image(images[[3]], colormode = 'Color'), method='r')
title('Imagen 3 original')
display(Image(images_sin_ruido[[2]], colormode = 'Color'), method='r')
title('Imagen 3 sin ruido')

display(Image(images[[3]], colormode = 'Color'), method='r')
title('Imagen 3 original')
display(Image(images_sin_ruido[[3]], colormode = 'Color'), method='r')
title('Imagen 3 sin ruido')
rm(images_recortadas, images_sin_ruido, images_sin_ruidos)

4.2.2 Ampliando las imágenes

Vamos ha emplear ahora el otro método: aumentando la matriz con 0 ó 1 hasta el tamaño adecuado para poder aplicar la función imwd. Comenzamos generando las imágenes con ruido. Se elige la foto con menor resolución (imagen 5) porque, al aplicar la función a fotos con mayor cantidad de píxeles, se genera un problema con el uso de la memoria en R para cargarlas debido a que la matriz se hace demasiado grande. Se aplican distintos ruidos a la misma imagen, se presenta una foto de ejemplo con ruido gaussiano.

Se genera una función que ajusta cualquier imagen rectangular a un tamaño cuadrado, manteniendo sus proporciones originales al agregar relleno si es necesario. Esta transformación asegura que la imagen sea compatible con el algoritmo IMWD.

hacer_cuadrada_potencia_2 <- function(imagen) {
  n_filas <- dim(imagen)[1]
  n_columnas <- dim(imagen)[2]
  
  nuevo_tamano <- max(n_filas, n_columnas)
  
  siguiente_potencia_2 <- 2^ceiling(log2(nuevo_tamano))
  
  imagen_cuadrada <- array(0, dim = c(siguiente_potencia_2, siguiente_potencia_2, dim(imagen)[3])) 
  
  imagen_cuadrada[1:n_filas, 1:n_columnas, ] <- imagen
  
  return(imagen_cuadrada)
}

Aplicamos esta función a la imagen 5 con los distintos tipos de ruido.

Se observa que el umbral FDR podría ofrecer una ligera mejora en la eliminación de ruido en comparación con el umbral Universal. Esto se debe a que el FDR estima la probabilidad de que un coeficiente de wavelet provenga del ruido, basándose en la distribución normal. Al asumir que el ruido sigue una distribución gaussiana, el FDR puede ajustar el umbral de manera más dinámica, lo que le permite identificar y eliminar los coeficientes ruidosos con mayor precisión, al tiempo que preserva los detalles relevantes de la imagen. En cambio, el umbral Universal utiliza un umbral fijo basado en el tamaño de la señal, lo que no captura completamente la variabilidad del ruido. Como resultado, el FDR podría ofrecer una eliminación de ruido más adaptativa, ayudando a preservar mejor los detalles de la imagen, aunque la diferencia en la práctica no siempre sea marcadamente significativa.

Eliminar Ruido Gaussiano

Eliminar Ruido Sinusoidal high

Eliminar Ruido Sinusoidal low http://localhost:10497/session/preview.png?viewer_pane=1&capabilities=1&host=http%3A%2F%2F127.0.0.1%3A29683

Eliminar Ruido Salt and pepper

Eliminar Ruido uniforme

Eliminar Ruido gamma

rm(gamma_noise_5,gaussian_noise_5, salt_pepper_noise_5,sinu_high_noise_5,sinu_low_noise_5,unif_noise_5,hacer_cuadrada_potencia_2,procesar_imagen_wavelet_sinusoidal, procesar_imagen_wavelet, resize_imwd, resize_imwd_to_original, imprimir, fotos_cuadradas, imagen_noise)
Aviso en rm(gamma_noise_5, gaussian_noise_5, salt_pepper_noise_5, sinu_high_noise_5,  :
  objeto 'gamma_noise_5' no encontrado
Aviso en rm(gamma_noise_5, gaussian_noise_5, salt_pepper_noise_5, sinu_high_noise_5,  :
  objeto 'gaussian_noise_5' no encontrado
Aviso en rm(gamma_noise_5, gaussian_noise_5, salt_pepper_noise_5, sinu_high_noise_5,  :
  objeto 'salt_pepper_noise_5' no encontrado
Aviso en rm(gamma_noise_5, gaussian_noise_5, salt_pepper_noise_5, sinu_high_noise_5,  :
  objeto 'sinu_high_noise_5' no encontrado
Aviso en rm(gamma_noise_5, gaussian_noise_5, salt_pepper_noise_5, sinu_high_noise_5,  :
  objeto 'sinu_low_noise_5' no encontrado
Aviso en rm(gamma_noise_5, gaussian_noise_5, salt_pepper_noise_5, sinu_high_noise_5,  :
  objeto 'unif_noise_5' no encontrado
Aviso en rm(gamma_noise_5, gaussian_noise_5, salt_pepper_noise_5, sinu_high_noise_5,  :
  objeto 'hacer_cuadrada_potencia_2' no encontrado
Aviso en rm(gamma_noise_5, gaussian_noise_5, salt_pepper_noise_5, sinu_high_noise_5,  :
  objeto 'procesar_imagen_wavelet_sinusoidal' no encontrado
Aviso en rm(gamma_noise_5, gaussian_noise_5, salt_pepper_noise_5, sinu_high_noise_5,  :
  objeto 'procesar_imagen_wavelet' no encontrado
Aviso en rm(gamma_noise_5, gaussian_noise_5, salt_pepper_noise_5, sinu_high_noise_5,  :
  objeto 'resize_imwd' no encontrado
Aviso en rm(gamma_noise_5, gaussian_noise_5, salt_pepper_noise_5, sinu_high_noise_5,  :
  objeto 'resize_imwd_to_original' no encontrado
Aviso en rm(gamma_noise_5, gaussian_noise_5, salt_pepper_noise_5, sinu_high_noise_5,  :
  objeto 'imprimir' no encontrado

4.3 Función denoise.dwt.2d

En segundo lugar, vamos a emplear la función denoise.dwt.2d, un método más directo que realiza la transformada wavelet, el thresholding y la transformada inversa en esta función ya implementada en R. A continuación se muestran los distintos filtros wavelet que se pueden escoger como parámetros de esta función.


# dir.create("denoisedwt2d", showWarnings = FALSE)

# png("denoisedwt2d/wavelet_filters.png", width = 800, height = 400)

filtro <- c('d4', 'la8', 'bl14', 'mb8')

par(mfrow = c(2, 4))
for (f in filtro) {
  # Obtener los coeficientes del filtro wavelet
  wavelet_filter <- wave.filter(f)
  
  # Ajustar coeficientes para que empiecen y terminen en 0
  h_adjusted <- c(0, wavelet_filter$hpf, 0)
  g_adjusted <- c(0, wavelet_filter$lpf, 0)
  
  # Calcular el rango simétrico para el eje Y
  max_val <- max(abs(c(h_adjusted, g_adjusted)))
  y_lim <- c(-max_val, max_val)
  
  # Graficar
  plot(h_adjusted, type = "l", main = paste("Paso alto", f),
       xlab = "Índice", ylab = "Amplitud", col = "blue", lwd = 2,
       ylim = y_lim)
  plot(g_adjusted, type = "l", main = paste("Paso bajo", f),
       xlab = "Índice", ylab = "Amplitud", col = "red", lwd = 2,
       ylim = y_lim)
}


# dev.off()

rm(h_adjusted)
rm(g_adjusted)
rm(max_val)
rm(y_lim)

Dado que se han realizado múltiples pruebas y los resultados son muy similares, se muestran solo aquellos que se han considerado más relevantes.

nivel <- c(2, 3, 4)
umbral <- c("soft", "hard")

Primero, mostramos el resultado del proceso de denoising de la imagen 1, a la que se le añadió ruido gaussiano. Este caso se utiliza como ejemplo porque ilustra las conclusiones que también son válidas para el resto de imágenes y tipos de ruido.


# Para evitar largos tiempos de procesado, se generaron las imágenes y se almacenaron en la carpeta denoisedwt2d, y serán las imagenes guardadas las que se muestren en el markdown.

# png("denoisedwt2d/gaussian.png", width = 800, height = 400)
# 
# i_sinruido <- images[[1]]
# i_ruidosa <-  add_noise_to_image("1", "gaussian", list(std_dev = 0.3))
#     
# i_dim <- dim(i_sinruido)
# i_len <- length(i_sinruido)
# 
# i_denoised <- array(0, dim = dim(i_ruidosa))
# 
# par(mfrow= c(1,2))
# display(Image(i_sinruido, colormode = "Color"), method = "r")
# title("Original")
# display(Image(i_ruidosa, colormode = "Color"), method = "r")
# title("Imagen con ruido")
# 
# dev.off()
# png("denoisedwt2d/gaussian_denoised.png", width = 1200, height = 1800)
# 
# par(mfcol= c(6,4))
# for (f in filtro){
#   for (n in nivel){
#     for (u in umbral){
#       canal1 <- denoise.dwt.2d(i_ruidosa[,,1], wf = f, rule = u, J = n)
#       canal2 <- denoise.dwt.2d(i_ruidosa[,,2], wf = f, rule = u, J = n)
#       canal3 <- denoise.dwt.2d(i_ruidosa[,,3], wf = f, rule = u, J = n)
#       i_denoised <- array(c(canal1, canal2, canal3), dim = c(nrow(canal1), ncol(canal1), 3))
#         
#       display(Image(i_denoised, colormode = "Color"), method = "r")
#       title(paste0("Wavelet ", f, ", umbral " , u, ", nivel ", n))
#     }}}
# 
# dev.off()

include_graphics("denoisedwt2d/gaussian.png")

include_graphics("denoisedwt2d/gaussian_denoised.png")

NA

Se observa que el uso de los diferentes filtros wavelet y reglas de aplicación del umbral no genera resultados con diferencias significativas. Sin embargo, el nivel de descomposición sí tiene un impacto notable.

Con un menor número de niveles (2-3), se logran conservar los detalles de la imagen, pero el ruido no se elimina completamente, con solo 2 niveles, el ruido sigue siendo evidente. Al aumentar a 4 niveles, se obtiene una imagen en la que el ruido es prácticamente inapreciable, aunque aparece algo más suavizada. Si se aumentaran aún más los niveles de descomposición, se empezarían a perder detalles importantes de la imagen. Con 4 niveles se consigue un buen equilibrio entre la eliminación del ruido y la conservación de los detalles. Este comportamiento se repite en los distintos tipos de ruido analizados.

A continuación mostramos solo algunas particularidades relevantes de los resultados:

Por ejemplo, en el caso del ruido sinusoidal de alta frecuencia, el resultado del proceso de eliminación de ruido es distinto.


# png("denoisedwt2d/sinhigh.png", width = 800, height = 400)
# 
# i_sinruido <- images[[2]]
# i_ruidosa <- add_noise_to_image("2", "sinusoidal_high", list(frequency = 25, amplitude = 0.2))
#     
# i_dim <- dim(i_sinruido)
# i_len <- length(i_sinruido)
# 
# i_denoised <- array(0, dim = dim(i_ruidosa))
# 
# par(mfrow= c(1,2))
# display(Image(i_sinruido, colormode = "Color"), method = "r")
# title("Original")
# display(Image(i_ruidosa, colormode = "Color"), method = "r")
# title("Imagen con ruido")
# 
# dev.off()
# png("denoisedwt2d/sinhigh_denoised.png", width = 1200, height = 1800)
# 
# par(mfcol= c(6,4))
# for (f in filtro){
#   for (n in nivel){
#     for (u in umbral){
#       canal1 <- denoise.dwt.2d(i_ruidosa[,,1], wf = f, rule = u, J = n)
#       canal2 <- denoise.dwt.2d(i_ruidosa[,,2], wf = f, rule = u, J = n)
#       canal3 <- denoise.dwt.2d(i_ruidosa[,,3], wf = f, rule = u, J = n)
#       i_denoised <- array(c(canal1, canal2, canal3), dim = c(nrow(canal1), ncol(canal1), 3))
#         
#       display(Image(i_denoised, colormode = "Color"), method = "r")
#       title(paste0("Wavelet ", f, ", umbral " , u, ", nivel ", n))
#   }}}
# 
# dev.off()

include_graphics("denoisedwt2d/sinhigh.png")

include_graphics("denoisedwt2d/sinhigh_denoised.png")

NA

En este caso, vemos que en ninguno de los casos logramos eliminar completamente el ruido sinusoidal. Esto puede deberse a que este tipo de ruido presenta componentes muy específicas de alta frecuencia, que pueden coincidir con las frecuencias de los detalles importantes de la imagen, lo que dificulta eliminar el ruido sin comprometer los detalles de la imagen. El ruido sinusoidal era también el más complicado de eliminar cuándo aplicabamos la función imwd y threshold.

Otro ruido que tiene resultados peculiares es el ruido de tipo salt-and-pepper.


# png("denoisedwt2d/pepper.png", width = 800, height = 400)
# 
# i_sinruido <- images[[4]]
# i_ruidosa <- add_noise_to_image("4", "salt_pepper", list(epsilon = 0.1))
#     
# i_dim <- dim(i_sinruido)
# i_len <- length(i_sinruido)
# 
# i_denoised <- array(0, dim = dim(i_ruidosa))
# 
# par(mfrow= c(1,2))
# display(Image(i_sinruido, colormode = "Color"), method = "r")
# title("Original")
# display(Image(i_ruidosa, colormode = "Color"), method = "r")
# title("Imagen con ruido")
# 
# dev.off()
# png("denoisedwt2d/pepper_denoised.png", width = 1200, height = 1800)
# 
# par(mfcol= c(6,4))
# for (f in filtro){
#   for (n in nivel){
#     for (u in umbral){
#       canal1 <- denoise.dwt.2d(i_ruidosa[,,1], wf = f, rule = u, J = n)
#       canal2 <- denoise.dwt.2d(i_ruidosa[,,2], wf = f, rule = u, J = n)
#       canal3 <- denoise.dwt.2d(i_ruidosa[,,3], wf = f, rule = u, J = n)
#       i_denoised <- array(c(canal1, canal2, canal3), dim = c(nrow(canal1), ncol(canal1), 3))
#         
#       display(Image(i_denoised, colormode = "Color"), method = "r")
#       title(paste0("Wavelet ", f, ", umbral " , u, ", nivel ", n))
#   }}}
# 
# dev.off()

include_graphics("denoisedwt2d/pepper.png")

include_graphics("denoisedwt2d/pepper_denoised.png")

NA

En este caso, de nuevo las diferencias más notables de deben a los distintos niveles de descomposición, sin embargo, también observamos diferencias sutiles en los resultados en cuanto a:

  • La regla de aplicación del umbral: Al utilizar la regla soft, se consigue una mejor eliminación del ruido. Esto podría deberse a que el ruido salt and pepper asigna valores cercanos a 0 (pimienta, negro) y cercanos a 1 (sal, blanco) a algunos píxeles. Al aplicar el método hard, no se atenúan adecuadamente los picos de ruido debido a su naturaleza más agresiva.

  • El filtro wavelet empleado: El rendimiento con el filtro d4 es inferior al de otros filtros. Esto se debe a que el filtro d4 es un filtro corto (con solo 4 coeficientes) y tiene una resolución de frecuencia limitada. Como resultado, no puede capturar eficazmente los picos abruptos del ruido, como los valores 0 y 1 del ruido salt and pepper.

Por último, presentamos los resultados del proceso de denoising de la imagen 5, a la que se le ha aplicado ruido gaussiano. A diferencia de la imagen 1, en este caso la resolución de la imagen es considerablemente inferior, lo que parece influir en los resultados obtenidos.


# png("denoisedwt2d/image5_noise.png", width = 800, height = 400)
# 
# i_sinruido <- images[[5]]
# i_ruidosa <- add_noise_to_image("5", "gaussian", list(std_dev = 0.3))
#     
# i_dim <- dim(i_sinruido)
# i_len <- length(i_sinruido)
# 
# i_denoised <- array(0, dim = dim(i_ruidosa))
# 
# par(mfrow= c(1,2))
# display(Image(i_sinruido, colormode = "Color"), method = "r")
# title("Original")
# display(Image(i_ruidosa, colormode = "Color"), method = "r")
# title("Imagen con ruido")
# 
# dev.off()
# png("denoisedwt2d/image5_denoised.png", width = 1200, height = 1800)
# 
# par(mfcol= c(6,4))
# for (f in filtro){
#   for (n in nivel){
#     for (u in umbral){
#       canal1 <- denoise.dwt.2d(i_ruidosa[,,1], wf = f, rule = u, J = n)
#       canal2 <- denoise.dwt.2d(i_ruidosa[,,2], wf = f, rule = u, J = n)
#       canal3 <- denoise.dwt.2d(i_ruidosa[,,3], wf = f, rule = u, J = n)
#       i_denoised <- array(c(canal1, canal2, canal3), dim = c(nrow(canal1), ncol(canal1), 3))
#         
#       display(Image(i_denoised, colormode = "Color"), method = "r")
#       title(paste0("Wavelet ", f, ", umbral " , u, ", nivel ", n))
#   }}}
# 
# dev.off()

include_graphics("denoisedwt2d/image5_noise.png")

include_graphics("denoisedwt2d/image5_denoised.png")

NA

La diferencia más notable en comparación con el resto de resultados, es que en este caso, al aumentar el numero de niveles de descomposición a un número que nos permita eliminar la mayor parte del ruido, la calidad de la imagen se ve significativamente afectada, y obtenemos una imagen excesivamente suavizada. Esto puede deberse a que a medida que aumentan los niveles de descomposición, la imagen se descompone en frecuencias cada vez más altas, provocando la pérdida de detalles finos.

La baja resolución de la imagen hace que sea más difícil mantener un equilibrio entre la eliminación del ruido y la preservación de los detalles. Al aumentar los niveles de descomposición, el ruido se elimina en mayor medida, pero también se pierde mucha información útil.

4.4 Función fftshift

Por último, vamos a ver que es lo que ocurre cuando en lugar de transformadas wavelet empleamos transformadas de fourier.

imgr_1 <- add_noise_to_image("1", "gaussian", list(std_dev = 0.3))
imgr_2 <- add_noise_to_image("2", "sinusoidal_high", list(frequency = 25, amplitude = 0.2))
imgr_3 <- add_noise_to_image("3", "sinusoidal_low", list(frequency = 2, amplitude = 0.2))
imgr_4 <- add_noise_to_image("4", "salt_pepper", list(epsilon = 0.1))
imgr_5 <- add_noise_to_image("5", "gamma", list(looks = 2))
imgr_6 <- add_noise_to_image("5", "uniform_multiplicative", list(looks = 2))

4.4.1 Imagen 1 y ruido gaussiano

4.4.2 Imagen 2 y ruido sinusoidal alto

4.4.3 Imagen 3 y ruido sinusoidal bajo

4.4.4 Imagen 4 y ruido sal y pimienta

4.4.5 Imagen 5 y ruido gamma

4.4.6 Imagen 5 y ruido multiplicativo uniforme

5 Conclusiones

LS0tDQp0aXRsZTogIkVsaW1pbmFjacOzbiBkZSBydWlkbyBlbiBpbcOhZ2VuZXMgY29uIHdhdmVsZXRzLiINCnN1YnRpdGxlOiAiQW7DoWxpc2lzIGRlIHNlw7FhbGVzIg0KYXV0aG9yOiAiR3J1cG8gRTogQWxlamFuZHJhIFZlbmVnYXMsIFJlYmVjYSBDb21wYW55LCBNYXJ0YSBNZWRpbmEsIEFsZWphbmRybyBDb3JuZWxpbyB5IElsaWEgWmhpZ2FyZXYuIg0KZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0Og0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2RlcHRoOiAzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgZWNobzogdHJ1ZQ0KICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQ0KICAgIHRoZW1lOiBsdW1lbg0KICAgIHRvYzogdHJ1ZQ0KICBib29rZG93bjo6cGRmX2RvY3VtZW50MjoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZGVwdGg6IDMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCiAgICBmaWd1cmVfY2FwdGlvbjogIkZpZ3VyYSIgIyBSZWZlcmVuY2lhcyBlbiBjYXN0ZWxsYW5vDQogICAgdGFibGVfY2FwdGlvbjogIlRhYmxhIg0KICBodG1sX25vdGVib29rOg0KICAgIGVjaG86IHRydWUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCiAgICB0b2M6IHRydWUNCiAgYm9va2Rvd246Omh0bWxfZG9jdW1lbnQyOg0KICAgIGVjaG86IHRydWUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICB0b2M6IHRydWUNCiAgICBmaWd1cmVfY2FwdGlvbjogIkZpZ3VyYSINCiAgICB0YWJsZV9jYXB0aW9uOiAiVGFibGEiDQphbHdheXNfYWxsb3dfaHRtbDogdHJ1ZQ0KcGFyYW1zOg0KICBsYW5nOiBFUw0KbGFuZzogImByIHN3aXRjaChwYXJhbXMkbGFuZywgRVMgPSAnZXMtRVMnLCBFTiA9ICdlbi1VUycpYCINCmxhbmd1YWdlOg0KICBsYWJlbDoNCiAgICBmaWc6ICdGaWd1cmEgJw0KICAgIHRhYjogJ1RhYmxhICcNCiAgICBlcTogJ0VjdWFjacOzbiAnDQogICAgdGhtOiAnVGVvcmVtYSAnDQogICAgbGVtOiAnTGVtYSAnDQogICAgZGVmOiAnRGVmaW5pY2nDs24gJw0KICAgIGNvcjogJ0Nvcm9sYXJpbyAnDQogICAgcHJwOiAnUHJvcG9zaWNpw7NuICcNCiAgICBleG06ICdFamVtcGxvICcNCiAgICBleHI6ICdFamVyY2ljaW8gJw0KICAgIHByb29mOiAnRGVtb3N0cmFjacOzbi4gJw0KICAgIHJlbWFyazogJ05vdGE6ICcNCiAgICBzb2x1dGlvbjogJ1NvbHVjacOzbi4gJw0KLS0tDQoNCiMgSW50cm9kdWNjacOzbiAgDQoNCkVuIGVsIGNhbXBvIGRlbCBhbsOhbGlzaXMgZGUgc2XDsWFsZXMsIHVubyBkZSBsb3MgcHJvYmxlbWFzIG3DoXMgcmVsZXZhbnRlcyBlcyBsYSBlbGltaW5hY2nDs24gZGUgcnVpZG8gZW4gaW3DoWdlbmVzIGRpZ2l0YWxlcy4gTGFzIHNlw7FhbGVzLCBxdWUgcmVwcmVzZW50YW4gdmFyaWFjaW9uZXMgZGUgbWFnbml0dWRlcyBlbiBlbCBlc3BhY2lvIHkvbyB0aWVtcG8sIGEgbWVudWRvIGVzdMOhbiBjb250YW1pbmFkYXMgcG9yIHJ1aWRvIGRlYmlkbyBhIGludGVyZmVyZW5jaWFzIGVuIGxvcyBwcm9jZXNvcyBkZSBjYXB0dXJhIG8gdHJhbnNtaXNpw7NuLiBFc3RlIHJ1aWRvLCBxdWUgcHVlZGUgZXhpc3RpciBkZSBmb3JtYXMgbXV5IGRpdmVyc2FzIGNvbW8gcnVpZG8gZ2F1c3NpYW5vLCBhcnRlZmFjdG9zIGFzb2NpYWRvcyBhIGZyZWN1ZW5jaWFzIGFsdGFzIG8gYmFqYXMsIG8gZWZlY3RvcyBtdWx0aXBsaWNhdGl2b3MsIGNvbXBsaWNhIGxhIGV4dHJhY2Npw7NuIGRlIGNhcmFjdGVyw61zdGljYXMgc2lnbmlmaWNhdGl2YXMuDQoNCkVsIHVzbyBkZSB3YXZlbGV0cyBlcyB1bmEgaGVycmFtaWVudGEgY2xhdmUgcGFyYSBhYm9yZGFyIGVzdGUgcHJvYmxlbWEsIGVsIGN1YWwgcGVybWl0ZSBkZXNjb21wb25lciBzZcOxYWxlcyBlbiBzdXMgY29tcG9uZW50ZXMgZXNwYWNpYWxlcyB5IGZyZWN1ZW5jaWFsZXMgZGUgZm9ybWEgZWZpY2llbnRlLiBFc3RlIG3DqXRvZG8gb2ZyZWNlIGxhIHBvc2liaWxpZGFkIGRlIGFkYXB0YXJzZSBhIGxhcyBjYXJhY3RlcsOtc3RpY2FzIHBhcnRpY3VsYXJlcyBkZSBjYWRhIHRpcG8gZGUgcnVpZG8uDQoNCkVuIGVzdGUgdHJhYmFqbyBzZSBwcm9wb25lIGV4cGxvcmFyIGxhIGNhcGFjaWRhZCBkZSBsb3Mgd2F2ZWxldHMgcGFyYSBsYSBlbGltaW5hY2nDs24gZGUgcnVpZG8gZW4gaW3DoWdlbmVzIGRpZ2l0YWxlcy4gUGFyYSBlbGxvLCBzZSBnZW5lcmFyw6FuIHkgYW5hbGl6YXLDoW4gZGlmZXJlbnRlcyB0aXBvcyBkZSBydWlkbyBhcnRpZmljaWFsLCBldmFsdWFuZG8gc3UgY29tcGxlamlkYWQgcGFyYSBzdSBhdGVudWFjacOzbiB5IGRldGVybWluYW5kbyBsb3MgcGFyw6FtZXRyb3MgbcOhcyBlZmljYWNlcyBkZSBsb3Mgd2F2ZWxldHMuICANCg0KIyBGdW5kYW1lbnRvIHRlw7NyaWNvOiBlbGltaW5hY2nDs24gZGUgcnVpZG8gY29uIHdhdmVsZXRzDQoNCkxvcyB3YXZlbGV0cyBzb24gZnVuY2lvbmVzIG1hdGVtw6F0aWNhcyBxdWUgcGVybWl0ZW4gZGVzY29tcG9uZXIgdW5hIHNlw7FhbCBlbiBjb21wb25lbnRlcyBkZSBkaWZlcmVudGVzIGVzY2FsYXMsIGxvIHF1ZSByZXN1bHRhIMO6dGlsIHBhcmEgaWRlbnRpZmljYXIgeSBwcm9jZXNhciBjYXJhY3RlcsOtc3RpY2FzIGVzcGVjw61maWNhcy4gRXN0ZSBlbmZvcXVlIGVzIGVzcGVjaWFsbWVudGUgcmVsZXZhbnRlIGVuIGxhIGVsaW1pbmFjacOzbiBkZSBydWlkbywgZG9uZGUgbGFzIGZyZWN1ZW5jaWFzIGluZGVzZWFkYXMgcHVlZGVuIHNlciBzZXBhcmFkYXMgeSByZWR1Y2lkYXMgc2luIGFmZWN0YXIgc2lnbmlmaWNhdGl2YW1lbnRlIGxhcyBjYXJhY3RlcsOtc3RpY2FzIHByaW5jaXBhbGVzIGRlIGxhIHNlw7FhbCBvcmlnaW5hbC4gQSBkaWZlcmVuY2lhIGRlIGxhIFRyYW5zZm9ybWFkYSBkZSBGb3VyaWVyLCBxdWUgb3BlcmEgZ2xvYmFsbWVudGUgeSBubyBvZnJlY2UgaW5mb3JtYWNpw7NuIHNvYnJlIGxhIGxvY2FsaXphY2nDs24gdGVtcG9yYWwgZGUgbG9zIGV2ZW50b3MsIGxvcyB3YXZlbGV0cyBwZXJtaXRlbiB1biBhbsOhbGlzaXMgbG9jYWxpemFkbywgZmFjaWxpdGFuZG8gbGEgaWRlbnRpZmljYWNpw7NuIGRlIHBhdHJvbmVzIHkgYW5vbWFsw61hcyBlbiBsb3MgZGF0b3MuDQoNCkVsIHByb2Nlc28gZGUgcmVkdWNjacOzbiBkZSBydWlkbyBjb24gd2F2ZWxldHMgZ2VuZXJhbG1lbnRlIGluY2x1eWUgdHJlcyBldGFwYXMgcHJpbmNpcGFsZXM6IGxhIGRlc2NvbXBvc2ljacOzbiBkZSBsYSBzZcOxYWwgdXRpbGl6YW5kbyB1bmEgd2F2ZWxldCBtYWRyZSwgbGEgbW9kaWZpY2FjacOzbiBkZSBsb3MgY29lZmljaWVudGVzIHdhdmVsZXQgbWVkaWFudGUgdMOpY25pY2FzIGRlIHVtYnJhbCwgeSBsYSByZWNvbnN0cnVjY2nDs24gZGUgbGEgc2XDsWFsLiBMYSBzZWxlY2Npw7NuIGRlIGxhIHdhdmVsZXQgbWFkcmUgYWRlY3VhZGEgeSBsb3MgcGFyw6FtZXRyb3MgZGUgdW1icmFsIHNvbiBhc3BlY3RvcyBjcsOtdGljb3MgcXVlIGRlYmVuIGFkYXB0YXJzZSBhIGxhcyBjYXJhY3RlcsOtc3RpY2FzIGVzcGVjw61maWNhcyBkZWwgcnVpZG8geSBsYSBzZcOxYWwuDQoNCkFkZW3DoXMsIGxvcyB3YXZlbGV0cyBvZnJlY2VuIHVuIGVuZm9xdWUgbXVsdGktcmVzb2x1Y2nDs24sIHBlcm1pdGllbmRvIHVuYSByZXByZXNlbnRhY2nDs24gZGV0YWxsYWRhIGRlIGxvcyBjb21wb25lbnRlcyBkZSBhbHRhIGZyZWN1ZW5jaWEsIGFzb2NpYWRvcyBmcmVjdWVudGVtZW50ZSBjb24gZWwgcnVpZG8sIG1pZW50cmFzIHByZXNlcnZhbiBsYXMgZXN0cnVjdHVyYXMgZ2xvYmFsZXMgZGUgYmFqYSBmcmVjdWVuY2lhLiBFc3RhIGNhcmFjdGVyw61zdGljYSBoYWNlIHF1ZSBsb3Mgd2F2ZWxldHMgc2VhbiBlc3BlY2lhbG1lbnRlIMO6dGlsZXMgZW4gYXBsaWNhY2lvbmVzIGRvbmRlIGxhIHByZWNpc2nDs24geSBsYSBpbnRlZ3JpZGFkIGRlIGxvcyBkYXRvcyBzb24gZXNlbmNpYWxlcywgY29tbyBlbiBpbcOhZ2VuZXMgbcOpZGljYXMsIHByb2Nlc2FtaWVudG8gZGUgYXVkaW8gbyBhbsOhbGlzaXMgZGUgZGF0b3MgY2llbnTDrWZpY29zLg0KDQpFbiBlc3RlIHRyYWJham8sIHNlIGVtcGxlYXLDoW4gd2F2ZWxldHMgY29tbyBoZXJyYW1pZW50YSBwcmluY2lwYWwgcGFyYSBlbGltaW5hciBkaWZlcmVudGVzIHRpcG9zIGRlIHJ1aWRvIHNpbnTDqXRpY28gZW4gaW3DoWdlbmVzLg0KDQoNCiMgRnVuY2lvbmVzIGRlIHByb2dyYW1hY2lvbiBlbXBsZWFkYXMgDQoNCkFudGVzIGRlIGNvbWVuemFyLCBpbnN0YWxhbW9zIHkvbyBjYXJnYW1vcyB0b2RvcyBsb3MgcGFxdWV0ZXMgcmVxdWVyaWRvcy4NCg0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9JzEwMCUnKSANCmBgYA0KDQpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQ0Kcm0obGlzdCA9IGxzKCkpDQppZiAoIXJlcXVpcmUoInBhY21hbiIpKSBpbnN0YWxsLnBhY2thZ2VzKCJwYWNtYW4iKQ0KbGlicmFyeShwYWNtYW4pDQpwX2xvYWQoaW1hZ2VyLCB3YXZldGhyZXNoLCBnZ3Bsb3QyLCBkcGx5ciwgU3BhdGlhbFBhY2ssIHdhdmVzbGltLCBFQkltYWdlLCBzdHJpbmdyLCBqcGVnLCBhYmluZCwgbWFnaWNrLCBrbml0cikNCmBgYA0KDQpTZSBoYSBlc2NvZ2lkbyBlbXBsZWFyIHRyZXMgbcOpdG9kb2xvZ8OtYXMgZGlzdGludGFzIHBhcmEgcmVhbGl6YXIgbGEgZWxpbWluYWNpw7NuIGRlIHJ1aWRvLiBEb3MgZGUgZWxsYXMgc2UgYmFzYW4gZW4gZW1wbGVhciBsYSB0cmFuc2Zvcm1hZGEgd2F2ZWxldC4gU2luIGVtYmFyZ28sIHRhbWJpw6luIHNlIGhhIG9wdGFkbyBwb3IgZW1wbGVhciBlbiB1biB0ZXJjZXIgbcOpdG9kbyBlbCBhbsOhbGlzaXMgbWVkaWFudGUgdHJhbnNmb3JtYWRhcyBkZSBGb3VyaWVyIGNvbiBlbCBvYmpldGl2byBkZSBlbmNvbnRyYXIgc2ltaWxpdHVkZXMgeSBkaWZlcmVuY2lhcyBjb24gcmVzcGVjdG8gYSBsb3MgbcOpdG9kb3MgcXVlIGVtcGxlYW4gd2F2ZWxldHMuDQoNCkNvbiByZXNwZWN0byBhIGxvcyBtw6l0b2RvcyBjb24gd2F2ZWx0cyB1dGlsaXphcmVtb3MgdW4gbcOpdG9kbyBtdXkgZGlyZWN0bywgZW1wbGVhbmRvIGxhIGZ1bmNpw7NuIGBkZW5vaXNlLmR3dC4yZGAgZGVsIHBhcXVldGUgYHdhdmVzbGltYCBkZSBSLiBQb3Igb3RybyBsYWRvLA0KZW1wbGVhcmVtb3MgdW4gbcOpdG9kbyBtw6FzIG1hbnVhbCBxdWUgY29uc2lzdGUgZW4gcmVhbGl6YXIgbGEgdHJhbnNmb3JtYWRhIHdhdmVsZXQgZGlzY3JldGEgY29uIGxhIGZ1bmNpw7NuIGBpbXdkYCwgYXBsaWNhciB1bmEgZnVuY2nDs24gcGFyYSByZWFsaXphciBlbCB0aHJlc2hvbGRpbmcgeSBhIGNvbnRpbnVhY2nDs24gcmVhbGl6YXIgbGEgdHJhbnNmb3JtYWRhIGludmVyc2EuIA0KDQpQYXJhIGZpbmFsaXphciwgc2UgcmVhbGl6YXLDoSBsYSBlbGltaW5hY2nDs24gZGUgcnVpZG8gYSB0cmF2w6lzIGRlIHRyYW5zZm9ybWFkYXMgZGUgRm91cmllciBjb24gbGEgZnVuY2nDs24gYGZmdHNoaWZ0YCB5IGNvbXBhcmFyZW1vcyBsb3MgcmVzdWx0YWRvcyBvYnRlbmlkb3MgY29uIGNhZGEgbcOpdG9kby4NCg0KKipNw6l0b2RvIDEuIEVsaW1pbmFjacOzbiBkZSBydWlkbyBjb24gZWwgYWxnb3JpdG1vIGRlIE1hbGxhdCoqDQoNCkVtcGxlYW1vcyBsYSBmdW5pY8OzbiBgaW13ZCgpYCBkZWwgcGFxdWV0ZSB3YXZldGhyZXNoLiBFc3TDoSBmdW5jacOzbiByZWFsaXphIHVuYQ0KdHJhbnNmb3JtYWRhIGRpc2NyZXRhIHdhdmVsZXQgZGUgYWN1ZXJkbyBjb24gZWwgYWxnb3JpdG1vIGRlIE1hbGxhdC4NCg0KLSBgaW13ZChpbWFnZSwgZmlsdGVyLm51bWJlcj0xMCwgZmFtaWx5PSJEYXViTGVBc3ltbSIsIHR5cGU9IndhdmVsZXQiLC4uLikuYA0KDQpFbCBhcmd1bWVudG8gYGltYWdlYCBkZSBsYSBmdW5jacOzbiBkZWJlIHNlciB1bmEgbWF0cml6IGN1YWRyYWRhIGN1eWEgZGltZW5zacOzbg0Kc2VhIHBvdGVuY2lhIGRlIGRvcy5gZmlsdGVyLm51bWJlcmAgZWxpZ2UgbGEgc3VhdmlkYWQgZGUgbGEgd2F2ZWxldCBhIGVtcGxlYXIsIHNpZW5kbyBwb3IgZGVmZWN0byAxMC4gYGZhbWlseWAgaW5kaWNhIGxhIGZhbWlsaWEgZGUgd2F2ZWxldHMgYSBlbXBsZWFyICgiRGF1YkV4UGhhc2UiIMOzICJEYXViTGVBc3ltbSIpLlBhcmEgdHJhdGFyIGxhcyBmcm9udGVyYXMsIG1hbnRlbmRyZW1vcyBlbCBwYXLDoW1ldHJvIGBiYyA9ICJwZXJpb2RpYyJgIHBvciBkZWZlY3RvLg0KDQpgYGB7cn0NCiMgUmVwcmVzZW50YWNpw7NuIGRlIGxhcyBkb3MgZmFtaWxpYXMgZGUgd2F2ZWxldHMgZW1wbGVhZGFzIHBvciBsYSBmdW5jacOzbiBpbXdkLg0KDQojIEdlbmVyYWNpw7NuIGRlIHVuIGZpbHRybyBjb24gbG9zIGNvZWZpY2llbnRlcyBjb3JyZXNwb25kaWVudGVzIGRlIGNhZGEgd2F2ZWxldA0Kd2F2ZV9jb2VmZnMgPC0gZmlsdGVyLnNlbGVjdChmaWx0ZXIubnVtYmVyID0gMTAsIGZhbWlseSA9ICJEYXViTGVBc3ltbSIpDQp3YXZlX2NvZWZmc18yIDwtIGZpbHRlci5zZWxlY3QoZmlsdGVyLm51bWJlciA9IDEwLCBmYW1pbHkgPSAiRGF1YkV4UGhhc2UiKQ0KDQojIEVqZSB0ZW1wb3JhbA0KeCA8LSBzZXFfYWxvbmcod2F2ZV9jb2VmZnMkSCkNCg0KIyAjIFZpc3VhbGl6YWNpw7NuIGRlIGxhcyB3YXZlbGV0cw0KcGFyKG1mcm93ID0gYygxLCAyKSkNCg0KcGxvdCh4LCB3YXZlX2NvZWZmcyRILCB0eXBlID0gImwiLCBsd2QgPSAyLA0KICAgICBtYWluID0gIldhdmVsZXQgRGF1YkxlQXN5bW0iLA0KICAgICB4bGFiID0gIngiLCB5bGFiID0gIkFtcGxpdHVkIikNCnBvaW50cyh4LCB3YXZlX2NvZWZmcyRILCBwY2ggPSAxOSkNCg0KcGxvdCh4LCB3YXZlX2NvZWZmc18yJEgsIHR5cGUgPSAibCIsIGx3ZCA9IDIsDQogICAgIG1haW4gPSAiV2F2ZWxldCBEYXViRXhQaGFzZSIsDQogICAgIHhsYWIgPSAieCIsIHlsYWIgPSAiQW1wbGl0dWQiKQ0KcG9pbnRzKHgsIHdhdmVfY29lZmZzXzIkSCwgcGNoID0gMTkpDQoNCg0Kcm0od2F2ZV9jb2VmZnMpDQpybSh3YXZlX2NvZWZmc18yKQ0Kcm0oeCkNCg0KYGBgDQoNClBhcmEgbGEgZWxpbWluYWNpw7NuIGRlIHJ1aWRvIGFwbGljYXJlbW9zIGxhIGZ1bmNpw7NuIGB0aHJlc2hvbGQoKWAgYWwgb2JqZXRvIHF1ZQ0KZGV2dWVsdmUgbGEgZnVuY2nDs24gYGltd2QoKWAuIA0KDQotIGB0aHJlc2hvbGQoaW13ZCwgbGV2ZWxzID0gMzoobmxldmVsc1dUKGltd2QpIC0gMSksIHR5cGUgPSAiaGFyZCIsIHBvbGljeSA9ICJ1bml2ZXJzYWwiLCBieS5sZXZlbCA9IEZBTFNFLCB2YWx1ZSA9IDAsIHJldHVybi50aHJlc2hvbGQgPSBGQUxTRSwgY29tcHJlc3Npb24gPSBUUlVFLCBRID0gMC4wNSwgLi4uKWANCg0KYGxldmVsc2AgZXMgZWwgbsO6bWVybyBkZSBuaXZlbGVzIGEgbG9zIGN1w6FsZXMgZGVzZWFtb3MgYXBsaWNhciB1biB1bWJyYWwsIG1pZW50cmFzIHF1ZSBgdHlwZWAgaW5kaWNhIHNpIHF1ZXJlbW9zIHVuIHVtYnJhbCBtw6FzIHN1YXZlICgic29mdCIpIMOzIG3DoXMgZnVlcnRlICgiaGFyZCIpLiBFbCBwYXLDoW1ldHJvIGBwb2xpY3lgIHNlbGVjY2lvbmEgbGEgdMOpY25pY2EgcGFyYSBlbGVnaXIgdW1icmFsLiBgYnkubGV2ZWwgPSBGQUxTRWAgc2lnbmlmaWNhIHF1ZSBzZSBhcGxpY2EgdW4gdW1icmFsIGdsb2JhbCBhIHRvZG9zIGxvcyBuaXZlbGVzIGluZGljYWRvcywgbWllbnRyYXMgcXVlIGBieS5sZXZlbCA9IFRSVUVgIGNhbGN1bGFyIHRocmVzaG9sZCBwYXJhIGNhZGEgbml2ZWwgcG9yIHNlYXBhcmFkby4gRWwgcGFyw6FtZXRybyBgdmFsdWVgLCBlbCB2YWxvciBkZWwgdW1icmFsLCBzZSB1c2Fyw6Egc2kgZWxlZ2ltb3MgdMOpY25pY2FzIG1hbnVhbGVzIGVuIGBwb2xpY3lgLiBTaSBxdWVyZW1vcyBvYnRlbmVyIGVsIHZhbG9yIHVtYnJhbCBhcGxpY2FkbywgcG9uZHJlbW9zIGByZXR1cm4udGhyZXNob2xkID0gVFJVRWAuIEZpbmFsbWVudGUsIGRlamFyZW1vcyBlbCBwYXLDoW1ldHJvIGBjb21wcmVzc2lvbiA9IFRSVUVgIHBvciBkZWZlY3RvLCBwYXJhIG9idGVybmVyIHVuIG9iamV0byBtw6FzIHBlcXVlw7FvLiANCg0KRHVyYW50ZSBlbCBkZXNhcnJvbGxvIGRlbCB0cmFiYWpvIHZhcmlhcmVtb3MgZXN0b3MgcGFyw6FtZXRyb3MgcGFyYSBvYnNlcnZhciBzdSBlZmVjdG8gZW4gbGEgZWxpbWluYWNpw7NuIGRlIGRpdmVyc29zIHRpcG9zIGRlIHJ1aWRvIHkgZW5jb250YXIgYXF1ZWxsb3MgcXVlIGdlbmVyZW4gdW4gbWVqb3IgcmVzdWx0YWRvIGVuIGNhZGEgY2Fzby4gVW5hIHZleiByZWFsaXphZG8gZWwgdGhyZXNob2xkaW5nLCBlbXBsZWFyZW1vcyBsYSBmdW5jacOzbiBgaW13cmAgcGFyYSByZWFsaXphciBsYSB0cmFuc2Zvcm1kYSB3YXZlbGV0IGludmVyc2EgeSBwb2RlciB2aXN1YWxpemFyIGxhIGltYWdlbiB0cmFzIGxhIGVsaW1pbmFjacOzbiBkZSBydWlkby4NCg0KKipNw6l0b2RvIDIuIEZ1bmNpw7NuIGRlbm9pc2UuZHd0LjJkKioNCg0KRW1wbGVhbW9zIGxhIGZ1bmNpw7NuIGRlbm9pc2UuZHd0LjJkIGRlbCBwYXF1ZXRlIHdhdmVzbGltIHBhcmEgZWxpbWluYXIgZWwgcnVpZG8gZGUgbGFzIGltw6FnZW5lcyBlbXBsZWFuZG8gbGEgdHJhbnNmb3JtYWRhIHdhdmVsZXQgZGlzY3JldGEgKERXVCkuDQoNCkVzdGEgZnVuY2nDs24gZGVzY29tcG9uZSBsYSBpbWFnZW4gZW4gY29lZmljaWVudGVzIHdhdmVsZXQsIGFwbGljYW5kbyB1biBmaWx0cm8gd2F2ZWxldCBzZWxlY2Npb25hZG8gKHdmKSBlbiBhbWJvcyBlamVzIChEV1QtMkQpLiBDb21vIHJlc3VsdGFkbywgbGEgaW1hZ2VuIHF1ZWRhIGRpdmlkaWRhIGVuIDQgY3VhZHJhbnRlczoNCg0KKiBBcHJveGltYWNpw7NuIChMTCk6IENvbXBvbmVudGVzIGRlIGJhamEgZnJlY3VlbmNpYSB0YW50byBlbiBsYXMgZmlsYXMgY29tbyBlbiBsYXMgY29sdW1uYXMuIEVzIGxhIHZlcnNpw7NuIHN1YXZpemFkYSBvICJib3Jyb3NhIiBkZSBsYSBpbWFnZW4sIHF1ZSBjYXB0dXJhIGxhcyBjYXJhY3RlcsOtc3RpY2FzIGdlbmVyYWxlcy4NCg0KKiBIb3Jpem9udGFsIChMSCk6IENvbXBvbmVudGVzIGRlIGJhamEgZnJlY3VlbmNpYSBlbiBsYXMgZmlsYXMgeSBhbHRhIGZyZWN1ZW5jaWEgZW4gbGFzIGNvbHVtbmFzLiBDb250aWVuZSBkZXRhbGxlcyBob3Jpem9udGFsZXMgZGUgbGEgaW1hZ2VuLg0KDQoqIFZlcnRpY2FsIChITCk6IENvbXBvbmVudGVzIGRlIGFsdGEgZnJlY3VlbmNpYSBlbiBsYXMgZmlsYXMgeSBiYWphIGZyZWN1ZW5jaWEgZW4gbGFzIGNvbHVtbmFzLiBSZXByZXNlbnRhIGxvcyBkZXRhbGxlcyB2ZXJ0aWNhbGVzIChib3JkZXMgbyBjYW1iaW9zIGhvcml6b250YWxlcyBlbiBsYSBpbWFnZW4pLg0KDQoqIERpYWdvbmFsIChISCk6IENvbnRpZW5lIGxhcyBjb21wb25lbnRlcyBkZSBhbHRhIGZyZWN1ZW5jaWEgdGFudG8gZW4gbGFzIGZpbGFzIGNvbW8gZW4gbGFzIGNvbHVtbmFzLg0KDQpFc3RhIGRlc2NvbXBvc2ljacOzbiBlbiA0IGN1YWRyYW50ZXMgc2UgcmVwaXRlIHRhbnRhcyB2ZWNlcyBjb21vIG5pdmVsZXMgKEopIHNlIGVzcGVjaWZpcXVlbiBlbiBsYSBmdW5jacOzbi4NCg0KRWwgc2lndWllbnRlIHBhc28gZXMgYXBsaWNhciB1biB1bWJyYWwgcGFyYSBhanVzdGFyIG8gZWxpbWluYXIgbG9zIGNvZWZpY2llbnRlcyBkZSBtw6FzIGFsdGEgZnJlY3VlbmNpYSBlbiBsb3MgZGUgY3VhZHJhbnRlcyBkZSBkZXRhbGxlIEhMLCBMSCB5IEhIIGVuIGNhZGEgbml2ZWwgZGUgZGVzY29tcG9zaWNpw7NuLiBDYWJlIGRlc3RhY2FyIHF1ZSBubyBzZSBhcGxpY2EgZWwgdW1icmFsIGFsIGN1YWRyYW50ZSBMTCwgeWEgcXVlIGNvbnRpZW5lIGxhcyBjb21wb25lbnRlcyBkZSBiYWphIGZyZWN1ZW5jaWEsIHF1ZSBnZW5lcmFsbWVudGUgbm8gZXN0w6FuIGFmZWN0YWRhcyBwb3IgZWwgcnVpZG8uDQoNCkRlc3B1w6lzIGRlIGFwbGljYXIgZWwgdW1icmFsIHBhcmEgc3VwcmltaXIgZWwgcnVpZG8sIGxhIGltYWdlbiBzZSByZWNvbnN0cnV5ZSBlbXBsZWFuZG8gbGEgdHJhbnNmb3JtYWRhIHdhdmVsZXQgaW52ZXJzYSAoSURXVCkuIENvbW8gc2Ugb2JzZXJ2YSwgZXN0YSBmdW5jacOzbiBlcyB1bmEgYXBsaWNhY2nDs24gbcOhcyBkaXJlY3RhIGRlbCBtw6l0b2RvIGFudGVyaW9yLCB5YSBpbXBsZW1lbnRhZGEgZW4gdW5hIMO6bmljYSBmdW5jacOzbi4gDQoNClBhcmEgZXhwbG9yYXIgbGFzIHBvc2liaWxpZGFkZXMgZGUgZXN0YSBmdW5jacOzbiwgcmVhbGl6YW1vcyBsYXMgc2lndWllbnRlcyBwcnVlYmFzOg0KDQoqIFRpcG9zIGRlIHJ1aWRvOg0KDQpFbXBsZWFyZW1vcyBsb3MgNiB0aXBvcyBkZSBydWlkbyBxdWUgaGVtb3MgZGVmaW5pZG8gYW50ZXJpb3JtZW50ZSBlbiBsYXMgaW3DoWdlbmVzIGRlIGxhIDEgYSBsYSA0IChsYXMgaW1hZ2VuZXMgMSB5IDIgbGFzIGVtcGxlYXJlbW9zIDIgdmVjZXMpLiBFbXBsZWFyZW1vcyBlbCBydWlkbyBHYXVzc2lhbm8gZW4gbGEgaW1hZ2VuIDUgcXVlIHByZXNlbnRhIG1lbm9yIHJlc29sdWNpw7NuLg0KDQoqIFByb2JhcmVtb3MgY29uIGRvcyByZWdsYXMgZGlmZXJlbnRlcyBkZSB1bWJyYWwgKHJ1bGUpOg0KDQogICsgaGFyZDogQW51bGEgbG9zIGNvZWZpY2llbnRlcyBwb3IgZGViYWpvIGRlbCB1bWJyYWwsIGFzw61nbsOhbmRvbGVzIHZhbG9yIDAuIEVzIHVuYSBvcGNpw7NuIG3DoXMgYWdyZXNpdmEuDQogICsgc29mdDogRW4gbHVnYXIgZGUgYW51bGFyIGRpcmVjdGFtZW50ZSBsb3MgY29lZmljaWVudGVzLCBsZXMgb3RvcmdhIHVuIHZhbG9yIGdyYWR1YWwgZW4gZnVuY2nDs24gZGUgc3UgY2VyY2Fuw61hIGFsIHVtYnJhbC4gUGVybWl0ZSB1bmEgcmVkdWNjacOzbiBkZWwgcnVpZG8gbWVub3MgYWJydXB0YS4NCiAgDQoqIE5pdmVsZXMgZGUgZGVzY29tcG9zaWNpw7NuIChKKToNCg0KUHJvYmFyZW1vcyB0cmVzIG5pdmVsZXMgZGUgZGVzY29tcG9zaWNpw7NuIGRpc3RpbnRvcy4gRWwgcnVpZG8gc3VlbGUgZW5jb250cmFyc2UgZW4gbGFzIGFsdGFzIGZyZWN1ZW5jaWFzIGRlIGxvcyBuaXZlbGVzIG3DoXMgYmFqb3MsIG1pZW50cmFzIHF1ZSBlbiBsb3Mgbml2ZWxlcyBhbHRvcyBzZSBlbmN1ZW50cmFuIGxvcyBkZXRhbGxlcyBtw6FzIGZpbm9zLiBFc3BlcmFtb3MgcXVlIHVuIG1heW9yIG5pdmVsIGRlIGRlc2NvbXBvc2ljacOzbiBnZW5lcmUgaW3DoWdlbmVzIG3DoXMgc3Vhdml6YWRhcywgcGVybyBjb24gbWVub3MgZGV0YWxsZXMuDQoNCiogRmlsdHJvcyB3YXZlbGV0OiANCg0KRmluYWxtZW50ZSwgcmVhbGl6YXJlbW9zIGxhIGRlc2NvbXBvc2ljacOzbiBkZSBsYSBpbWFnZW4gY29uIDQgZmlsdHJvcyB3YXZlbGV0IGRpc3RpbnRvcywgc29uIDQgd2F2ZWxldHMgY29uIGRpZmVyZW50ZXMgZm9ybWFzIChwdWVkZW4gc2VyIHNpbcOpdHJpY2FzIG8gbm8pLCB5IGRpZmVyZW50ZSBudW1lcm8gZGUgY29lZmljaWVudGVzLg0KDQogICsgZDQgKERhdWJlY2hpZXMgNCkNCiAgKyBsYTggKExlYXN0IEFzeW1tZXRyaWMgOCkNCiAgKyBibDE0IChCZXN0IExvY2FsaXplZCAxNCkNCiAgKyBtYjggKE1heGltdW0gRmxhdCA4KQ0KICANCioqTcOpdG9kbyAzLiBUcmFuc2Zvcm1hZGEgZGUgRm91cmllciAoZnVuY2nDs24gZmZ0c2hpZnQpKioNCg0KKEJvcnJhZG9yKQ0KDQpFbCBtw6l0b2RvIHVzYWRvIHVzYSBsYSB0cmFuc2Zvcm1hZGEgcsOhcGlkYSBkZSBmb3VyaWVyIHBhcmEgcG9kZXIgZWxpbWluYXIgYWx0YXMgZnJlY3VlbmNpYXMgY29ycmVzcG9uZGllbnRlcyBhIHJ1aWRvIGVuIGxhIGltYWdlbi4gTGEgZnVuY2nDs24gKmZmdHNoaWZ0KiBkZXNwbGF6YSBsYXMgYmFqYXMgZnJlY3VlbmNpYXMgYWwgY2VudHJvIGRlbCBlc3BlY3RybyB5IGxhcyBhbHRhcyBhIGxvcyBleHRyZW1vcy4gVW5hIHZleiBzZSB2aXN1YWxpemEgZWwgZXNwZWN0cm8sIHNlIHByb2NlZGUgYSBkaXNtaW51aXIgbGEgbWFnbml0dWQgZGUgbGFzIGFsdGFzIGZyZWN1ZW5jaWFzIGNlcmNhbmFzIGEgbG9zIGV4dHJlbW9zIGFzaWduYW5kb2xlcyB1bmEgbWFnbml0dWQgbWVub3IuIENvbiBlc3RlIHByb2NlZGltaWVudG8gZXN0YW1vcyBhcGxpY2FuZG8gdW4gZmlsdHJvIGRlIHBhc28gYmFqbyB5IGFwbGljYW5kbyAqaWZmdHNoaWZ0KiBvYnRlbmVtb3MgbGEgaW1hZ2VuIGVuIGVsIGRvbWluaW8gZXNwYWNpYWwgY29uIHJ1aWRvIGVsaW1pbmFkby4NCg0KQWRlbcOhcywgYSBsbyBsYXJnbyBkZWwgdHJhYmFqbyBzZSBwcm9ncmFtYXIgZGl2ZXJzYXMgZnVuY2lvbmVzIHF1ZSBheXVkYXLDoW4gYSBhdXRpbWF0aXphciBlbCBwcm9jZXNvLg0KDQojIERlc2Fycm9sbG8geSByZXN1bHRhZG9zDQoNCg0KQ29tZW56YW1vcyBjYXJnYW5kbyB5IHZpc3VhbGl6YW5kbyBsYXMgZm90b2dyYWbDrWFzIGEgZW1wbGVhci4NCmBgYHtyfQ0KaW1hZ2VzX3BhdGggPC0gbGlzdC5maWxlcygiLi9mb3RvcyIsIGZ1bGwubmFtZXMgPSBUUlVFKQ0KDQpub21icmVzX2ltYWdlcyA8LSBzdHJfcmVtb3ZlX2FsbChzdHJpbmcgPSBzdHJfcmVtb3ZlX2FsbChzdHJpbmcgPSBpbWFnZXNfcGF0aCwgcGF0dGVybiA9ICIuL2ZvdG9zLyIpLCBwYXR0ZXJuID0gIlxcLkpQR3xcXC5qcGciKQ0KDQppbWFnZXMgPC0gbGFwcGx5KGltYWdlc19wYXRoLCByZWFkSlBFRykgIyBDYXJnYW1vcyBsYXMgaW3DoWdlbmVzDQpuYW1lcyhpbWFnZXMpIDwtIG5vbWJyZXNfaW1hZ2VzDQoNCnJtKGltYWdlc19wYXRoKQ0KYGBgDQoNCmBgYHtyfQ0KIyBSb3RhbW9zIGFsZ3VuYXMgZGUgbGFzIGZvdG9zIHBhcmEgdW5hIHZpc3VhbGl6YWNpw7NuIG3DoXMgdW5pZm9ybWUNCmZvdG9zX2FfZ2lyYXIgPC0gYygiMSIsICIyIiwgIjMiLCAiNCIpDQppbWFnZXNfcm90YWRhcyA8LSBsYXBwbHkoaW1hZ2VzW2ZvdG9zX2FfZ2lyYXJdLCBhcGVybSwgcGVybSA9IGMoMiwgMSwgMykpDQoNCg0KZm9yIChpIGluIGZvdG9zX2FfZ2lyYXIpIHsNCiAgaW1hZ2VzX3JvdGFkYXNbW2ldXSA8IGltYWdlc19yb3RhZGFzW1tpXV1bZGltKGltYWdlc19yb3RhZGFzW1tpXV0pWzFdOjEsICwgXQ0KfQ0KDQppbWFnZXNbZm90b3NfYV9naXJhcl0gPC0gaW1hZ2VzX3JvdGFkYXMNCnJtKGltYWdlc19yb3RhZGFzKQ0Kcm0oZm90b3NfYV9naXJhcikNCmBgYA0KDQpWYW1vcyBhIHZpc3VhbGl6YXIgbGFzIGltw6FnZW5lcyBlbXBsZWFkYXMuIFNlIGhhbiB0b21hZG8gNSBpbcOhZ2VuZXMgZGlzdGludGFzIHBlcm8gdG9kYXMgY29uIGxhIG1pc21hIHRlbcOhdGljYS4gTGEgZWxlY2Npw7NuIHNlIGhhIHJlYWxpemFkbyBwZW5zYW5kbyBlbiBwb2RlciBvYnNlcnZhciBjb21vIGFmZWN0YSBsYSBlbGltYWNpw7NuIGRlIHJ1aWRvIGRldGFsbGVzIGNvbW8gbGEgdGV4dHVyYSBvIGRlZmVjdG9zIGVuIGxhcyBmcnV0YXMuIEFkZW3DoXMsIGxhcyBpbcOhZ2VuZXMgMSBhIDQgc2UgaGFuIHRvbWFkbyBjb24gdW5hIGPDoW1hcmEgZGUgZ3JhbiBjYWxpZGFkLCBtaWVudHJhcyBxdWUgbGEgaW1hZ2VuIDUgY3VlbnRhIGNvbiB1bmEgcmVzb2x1Y2nDs24gbXVjaG8gbWVub3IuIFNlIGJ1c2NhIHRhbWJpw6luIGVzdHVkaWFyIHF1ZSBlZmVjdG8gdGllbmUgbGEgcmVzb2x1Y2nDs24gZGUgbGEgaW1hZ2VuIHkgc3UgY2FsaWRhZGEgZW4gbGEgZWxpbWluYWNpw7NuIGRlIHJ1aWRvLg0KYGBge3J9DQojIFZpc3VhbGl6YW1vcyBsYXMgaW1hZ2VuZXMgb3JpZ2luYWxlcyANCnBhcihtZnJvdyA9IGMoMiwgMyksIG1hciA9IGMoMSwgMSwgMSwgMSkpDQoNCiBmb3IgKGltZyBpbiBub21icmVzX2ltYWdlcykgew0KICAgZGlzcGxheShJbWFnZShpbWFnZXNbW2ltZ11dLCBjb2xvcm1vZGUgPSAiQ29sb3IiKSwgbWV0aG9kID0gInIiKQ0KICAgdGl0bGUocGFzdGUoJ0ltYWdlbicsIGltZykpDQogfQ0KYGBgDQoNCg0KIyMgSW5jbHVzacOzbiBkZSBydWlkbyBzaW50w6l0aWNvIGVuIGxhcyBpbcOhZ2VuZXMgIA0KDQpTZSBjcmVhbiBsYSBmdW5jacOzbiBgYWRkX25vaXNlX3RvX2ltYWdlYCB5IGBOT0lTRV9UWVBFU2AgcGFyYSBhw7FhZGlyIGVsIHJ1aWRvIGEgbGFzIGltw6FnZW5lcy4gTG9zIHJ1aWRvcyBxdWUgc2UgaGFuIGdlbmVyYWRvLCBjb24gcGFyw6FtZXRyb3MgYWp1c3RhYmxlcyBhIHZhcmlhciwgc29uIGxvcyBzaWd1aWVudGVzOg0KDQotIFJ1aWRvIGdhdXNzaWFubyBkZSBtZWRpYSAwIHkgZGVzdmlhY2nDs24gdMOtcGljYSBhanVzdGFibGUuDQotIFJ1aWRvcyBzaW51c29pZGFsZXMgZGUgYWx0YSB5IGJhamEgZnJlY3VlbmNpYS4NCi0gUnVpZG8gc2FsIHkgcGltaWVudGE6IEVzdGUgdGlwbyBkZSBydWlkbyBzdXN0aXR1eWUgcMOteGVsZXMgYWxlYXRvcmlvcyBlbiBsYSBpbWFnZW4gcG9yIHZhbG9yZXMgbcOtbmltb3MgKG5lZ3JvcykgbyBtw6F4aW1vcyAoYmxhbmNvcyksIHNpbXVsYW5kbyB1biBwYXRyw7NuIGRlIHB1bnRvcyBvc2N1cm9zIHkgYnJpbGxhbnRlcw0KLSBSdWlkbyBnYW1tYSBtdWx0aXBsaWNhdGl2bzogbXVsdGlwbGljYSBlbCB2YWxvciBkZSBsb3MgcMOteGVsZXMgcG9yIHVuYSBkaXN0cmlidWNpw7NuIGdhbW1hLg0KLSBSdWlkbyB1bmlmb3JtZSBtdWx0aXBsaWNhdGl2bzogbXVsdGlwbGljYSBlbCB2YWxvciBkZSBsb3MgcMOteGVsZXMgcG9yIHVuYSBkaXN0cmlidWNpw7NuIHVuaWZvcm1lLg0KDQpFc3RvcyBydWlkb3Mgc2ludMOpdGljb3MgcHVlZGVuIGltaXRhciBydWlkb3MgcXVlIHNlIGVuY3VlbnRyZW4gZW4gaW3DoWdlbmVzIHJlYWxlcy4gUG9yIGVqZW1wbG8sIGVsIHJ1aWRvIHNpbnVzb2lkYWwgaW50cm9kdWNlIHVuIHBhdHLDs24gcGVyacOzZGljbyBkZSBvc2NpbGFjaW9uZXMgcXVlIHB1ZWRlbiBzaW11bGFyIGludGVyZmVyZW5jaWFzIHBlcmnDs2RpY2FzLiBPdHJvIGVqZW1wbG8gZXMgZWwgcnVpZG8gc2FsIHkgcGltaWVudGEsIHF1ZSBwdWVkZSBzaW11bGFyIGZhbGxvcyBlbiBsYSBjYXB0dXJhIGRlIGxhcyBpbcOhZ2VuZXMgKGxhIGFwYXJpY2nDs24gZGUgcHVudG9zIGJsYW5jb3MgeSBuZWdyb3MpLg0KDQpgYGB7ciwgZmlnLmhlaWdodCA9IDR9DQojIERlZmluaWNpw7NuIGRlIHRpcG9zIGRlIHJ1aWRvDQoNCk5PSVNFX1RZUEVTIDwtIGxpc3QoDQogIGdhdXNzaWFuID0gbGlzdCgNCiAgICBnZW5lcmF0b3IgPSBmdW5jdGlvbihjaGFubmVsLCBwYXJhbXMpIHsNCiAgICAgICMgRGVzdmlhY2nDs24gZXN0w6FuZGFyIGRlbCBydWlkbyBjb24gdW4gdmFsb3IgcHJlZGV0ZXJtaW5hZG8NCiAgICAgIG5vaXNlX3N0ZF9kZXYgPC0gcGFyYW1zJHN0ZF9kZXYgJXx8JSAwLjUNCg0KICAgICAgIyBHZW5lcmFjacOzbiBkZSBydWlkbyBnYXVzc2lhbm8NCiAgICAgIG5vaXNlIDwtIGFycmF5KA0KICAgICAgICBybm9ybShsZW5ndGgoY2hhbm5lbCksIG1lYW4gPSAwLCBzZCA9IG5vaXNlX3N0ZF9kZXYpLA0KICAgICAgICBkaW0gPSBkaW0oY2hhbm5lbCkNCiAgICAgICkNCg0KICAgICAgIyBBc2VndXJhciBxdWUgbG9zIHZhbG9yZXMgZXN0w6luIGVudHJlIDAgeSAxDQogICAgICBwbWF4KDAsIHBtaW4oMSwgY2hhbm5lbCArIG5vaXNlKSkNCiAgICB9DQogICksDQogIHNpbnVzb2lkYWxfaGlnaCA9IGxpc3QoDQogICAgZ2VuZXJhdG9yID0gZnVuY3Rpb24oY2hhbm5lbCwgcGFyYW1zKSB7DQogICAgICAjIEZyZWN1ZW5jaWEgeSBhbXBsaXR1ZCBkZWwgcnVpZG8gc2ludXNvaWRhbCBkZSBhbHRhIGZyZWN1ZW5jaWENCiAgICAgIGZyZXF1ZW5jeSA8LSBwYXJhbXMkZnJlcXVlbmN5ICV8fCUgMjUNCiAgICAgIGFtcGxpdHVkZSA8LSBwYXJhbXMkYW1wbGl0dWRlICV8fCUgMC4yDQoNCiAgICAgICMgR2VuZXJhY2nDs24gZGUgcnVpZG8gc2ludXNvaWRhbA0KICAgICAgaGVpZ2h0IDwtIGRpbShjaGFubmVsKVsxXQ0KICAgICAgd2lkdGggPC0gZGltKGNoYW5uZWwpWzJdDQogICAgICB4IDwtIHNlcSgwLCAyICogcGksIGxlbmd0aC5vdXQgPSB3aWR0aCkNCiAgICAgIHkgPC0gc2VxKDAsIDIgKiBwaSwgbGVuZ3RoLm91dCA9IGhlaWdodCkNCiAgICAgIG5vaXNlX2dyaWQgPC0gb3V0ZXIoc2luKHggKiBmcmVxdWVuY3kpLCBzaW4oeSAqIGZyZXF1ZW5jeSkpDQoNCiAgICAgICMgQXBsaWNhciBlbCBydWlkbw0KICAgICAgbm9pc2UgPC0gYXJyYXkobm9pc2VfZ3JpZCAqIGFtcGxpdHVkZSwgZGltID0gZGltKGNoYW5uZWwpKQ0KICAgICAgcG1heCgwLCBwbWluKDEsIGNoYW5uZWwgKyBub2lzZSkpDQogICAgfQ0KICApLA0KICBzaW51c29pZGFsX2xvdyA9IGxpc3QoDQogICAgZ2VuZXJhdG9yID0gZnVuY3Rpb24oY2hhbm5lbCwgcGFyYW1zKSB7DQogICAgICAjIEZyZWN1ZW5jaWEgeSBhbXBsaXR1ZCBkZWwgcnVpZG8gc2ludXNvaWRhbCBkZSBiYWphIGZyZWN1ZW5jaWENCiAgICAgIGZyZXF1ZW5jeSA8LSBwYXJhbXMkZnJlcXVlbmN5ICV8fCUgMg0KICAgICAgYW1wbGl0dWRlIDwtIHBhcmFtcyRhbXBsaXR1ZGUgJXx8JSAwLjINCg0KICAgICAgIyBHZW5lcmFjacOzbiBkZSBydWlkbyBzaW51c29pZGFsDQogICAgICBoZWlnaHQgPC0gZGltKGNoYW5uZWwpWzFdDQogICAgICB3aWR0aCA8LSBkaW0oY2hhbm5lbClbMl0NCiAgICAgIHggPC0gc2VxKDAsIDIgKiBwaSwgbGVuZ3RoLm91dCA9IHdpZHRoKQ0KICAgICAgeSA8LSBzZXEoMCwgMiAqIHBpLCBsZW5ndGgub3V0ID0gaGVpZ2h0KQ0KICAgICAgbm9pc2VfZ3JpZCA8LSBvdXRlcihzaW4oeCAqIGZyZXF1ZW5jeSksIHNpbih5ICogZnJlcXVlbmN5KSkNCg0KICAgICAgIyBBcGxpY2FyIGVsIHJ1aWRvDQogICAgICBub2lzZSA8LSBhcnJheShub2lzZV9ncmlkICogYW1wbGl0dWRlLCBkaW0gPSBkaW0oY2hhbm5lbCkpDQogICAgICBwbWF4KDAsIHBtaW4oMSwgY2hhbm5lbCArIG5vaXNlKSkNCiAgICB9DQogICksDQogIHNhbHRfcGVwcGVyID0gbGlzdCgNCiAgICBnZW5lcmF0b3IgPSBmdW5jdGlvbihjaGFubmVsLCBwYXJhbXMpIHsNCiAgICAgICMgUHJvcG9yY2nDs24gZGUgcMOteGVsZXMgYWZlY3RhZG9zIHBvciBlbCBydWlkbyBkZSBzYWwgeSBwaW1pZW50YQ0KICAgICAgZXBzaWxvbiA8LSBwYXJhbXMkZXBzaWxvbiAlfHwlIDAuMg0KDQogICAgICAjIEdlbmVyYWNpw7NuIGRlIHJ1aWRvDQogICAgICBub2lzZSA8LSBtYXRyaXgoc2FtcGxlKGMoMCwgMSwgTkEpLCBsZW5ndGgoY2hhbm5lbCksIHJlcGxhY2UgPSBUUlVFLCBwcm9iID0gYyhlcHNpbG9uIC8gMiwgZXBzaWxvbiAvIDIsIDEgLSBlcHNpbG9uKSksDQogICAgICAgIG5yb3cgPSBkaW0oY2hhbm5lbClbMV0sIG5jb2wgPSBkaW0oY2hhbm5lbClbMl0NCiAgICAgICkNCiAgICAgIGNoYW5uZWxbIWlzLm5hKG5vaXNlKV0gPC0gbm9pc2VbIWlzLm5hKG5vaXNlKV0NCiAgICAgIGNoYW5uZWwNCiAgICB9DQogICksDQogIGdhbW1hID0gbGlzdCgNCiAgICBnZW5lcmF0b3IgPSBmdW5jdGlvbihjaGFubmVsLCBwYXJhbXMpIHsNCiAgICAgICMgUnVpZG8gbXVsdGlwbGljYXRpdm8gZ2FtbWEgY29uIHBhcsOhbWV0cm8gZGUgZGlzcGVyc2nDs24NCiAgICAgIGxvb2tzIDwtIHBhcmFtcyRsb29rcyAlfHwlIDINCiAgICAgIG5vaXNlIDwtIGFycmF5KHJnYW1tYShsZW5ndGgoY2hhbm5lbCksIHNoYXBlID0gbG9va3MsIHNjYWxlID0gMSAvIGxvb2tzKSwgZGltID0gZGltKGNoYW5uZWwpKQ0KICAgICAgcG1heCgwLCBwbWluKDEsIGNoYW5uZWwgKiBub2lzZSkpDQogICAgfQ0KICApLA0KICB1bmlmb3JtX211bHRpcGxpY2F0aXZlID0gbGlzdCgNCiAgICBnZW5lcmF0b3IgPSBmdW5jdGlvbihjaGFubmVsLCBwYXJhbXMpIHsNCiAgICAgICMgUnVpZG8gbXVsdGlwbGljYXRpdm8gdW5pZm9ybWUNCiAgICAgIGxvb2tzIDwtIHBhcmFtcyRsb29rcyAlfHwlIDINCiAgICAgIG5vaXNlX2NoYW5uZWwgPC0gU3BhdGlhbFBhY2s6Omltbm9pc2UoDQogICAgICAgIGltZyA9IGNoYW5uZWwsDQogICAgICAgIHR5cGUgPSAic3BlY2tsZSIsDQogICAgICAgIGxvb2tzID0gbG9va3MNCiAgICAgICkNCiAgICAgIHBtYXgoMCwgcG1pbigxLCBub2lzZV9jaGFubmVsKSkNCiAgICB9DQogICkNCikNCmBgYA0KDQoNCmBgYHtyLCBmaWcuaGVpZ2h0ID0gNH0NCiMgRnVuY2nDs24gcGFyYSBhw7FhZGlyIHJ1aWRvIGEgdW5hIGltYWdlbg0KYWRkX25vaXNlX3RvX2ltYWdlIDwtIGZ1bmN0aW9uKGltYWdlX25hbWUsIG5vaXNlX3R5cGUsIG5vaXNlX3BhcmFtcyA9IGxpc3QoKSwgcGxvdCA9IEZBTFNFKSB7DQogICMgVmVyaWZpY2FyIHNpIGxhIGltYWdlbiBleGlzdGUgZW4gbGEgbGlzdGENCiAgaWYgKCFpbWFnZV9uYW1lICVpbiUgbmFtZXMoaW1hZ2VzKSkgew0KICAgIHN0b3AoIkxhIGltYWdlbiBjb24gZXN0ZSBub21icmUgbm8gc2UgZW5jdWVudHJhIGVuIGxhIGxpc3RhICdpbWFnZXMnIikNCiAgfQ0KDQogICMgVmVyaWZpY2FyIGVsIHRpcG8gZGUgcnVpZG8NCiAgaWYgKCFub2lzZV90eXBlICVpbiUgbmFtZXMoTk9JU0VfVFlQRVMpKSB7DQogICAgc3RvcCgNCiAgICAgICJFbCB0aXBvIGRlIHJ1aWRvIGVzIGRlc2Nvbm9jaWRvLiBUaXBvcyBkaXNwb25pYmxlczogIiwNCiAgICAgIHBhc3RlKG5hbWVzKE5PSVNFX1RZUEVTKSwgY29sbGFwc2UgPSAiLCAiKQ0KICAgICkNCiAgfQ0KDQogICMgT2J0ZW5lciBsYSBpbWFnZW4gb3JpZ2luYWwgZGUgbGEgbGlzdGENCiAgb3JpZ2luYWxfaW1hZ2UgPC0gaW1hZ2VzW1tpbWFnZV9uYW1lXV0NCg0KICAjIENvbnZlcnRpciBsYSBpbWFnZW4gYSB1biBhcnJheSBzaSBlcyBuZWNlc2FyaW8NCiAgaW1hZ2VfYXJyYXkgPC0gYXMuYXJyYXkob3JpZ2luYWxfaW1hZ2UpDQoNCiAgIyBBcGxpY2FyIHJ1aWRvIGEgY2FkYSBjYW5hbA0KICBub2lzeV9jaGFubmVscyA8LSBsYXBwbHkoMTozLCBmdW5jdGlvbihpKSB7DQogICAgY2hhbm5lbCA8LSBpbWFnZV9hcnJheVssICwgaV0NCiAgICBOT0lTRV9UWVBFU1tbbm9pc2VfdHlwZV1dJGdlbmVyYXRvcihjaGFubmVsLCBub2lzZV9wYXJhbXMpDQogIH0pDQoNCiAgIyBDcmVhciBsYSBpbWFnZW4gY29uIHJ1aWRvDQogIG5vaXN5X2ltYWdlX2FycmF5IDwtIGFycmF5KA0KICAgIHVubGlzdChub2lzeV9jaGFubmVscyksDQogICAgZGltID0gZGltKGltYWdlX2FycmF5KQ0KICApDQoNCg0KICAjIFZpc3VhbGl6YXIgc2kgc2UgaGEgaW5kaWNhZG8NCiAgaWYgKHBsb3QgPT0gVFJVRSl7DQogIGxheW91dChtYXRyaXgoMToyLCAxLCAyKSkNCiAgcGxvdChJbWFnZShvcmlnaW5hbF9pbWFnZSwgY29sb3Jtb2RlID0gIkNvbG9yIikpDQogIHRpdGxlKCJPcmlnaW5hbCIpDQogIHBsb3QoSW1hZ2UoKG5vaXN5X2ltYWdlX2FycmF5KSwgY29sb3Jtb2RlID0gIkNvbG9yIikpDQogIHRpdGxlKHBhc3RlKCJSdWlkbzoiLCBub2lzZV90eXBlKSl9DQogIA0KICByZXR1cm4obm9pc3lfaW1hZ2VfYXJyYXkpDQp9DQoNCmBgYA0KDQoNCg0KDQojIyBGdW5jacOzbiBpbXdkDQoNCkVsIHVzbyBkZSBsYSBUcmFuc2Zvcm1hZGEgRGlzY3JldGEgZGUgV2F2ZWxldCAoRFdUKSBwZXJtaXRlIHNlcGFyYXIgbGFzIGZyZWN1ZW5jaWFzIGJhamFzIGRlIGxhcyBmcmVjdWVuY2lhcyBhbHRhcyAocXVlIHNvbiBsYXMgcXVlIHN1ZWxlbiBjb250ZW5lciBlbCBydWlkbykuDQoNClNlIGFwbGljYSBsYSBEV1QgZW4gdW5hIGltYWdlbiBcKCBmKHgsIHkpIFwpIGRlIHRhbWHDsW8gXCggTSBcdGltZXMgTiBcKSBjb24gZmlsdHJvcyBwYXNvLWJham8geSBwYXNvLWFsdG8gZW4gY2FkYSBkaW1lbnNpw7NuOg0KDQotIFwoIEhfMSBcKSB5IFwoIEhfMiBcKSBzb24gbG9zIGZpbHRyb3MgcGFzby1iYWpvIHkgcGFzby1hbHRvIGVuIGxhIGRpcmVjY2nDs24gaG9yaXpvbnRhbCwgcmVzcGVjdGl2YW1lbnRlLg0KLSBcKCBWXzEgXCkgeSBcKCBWXzIgXCkgc29uIGxvcyBmaWx0cm9zIHBhc28tYmFqbyB5IHBhc28tYWx0byBlbiBsYSBkaXJlY2Npw7NuIHZlcnRpY2FsLg0KDQpMYSBEV1Qgc2UgZGVzY29tcG9uZSBlbiBjdWF0cm8gc3ViLWltw6FnZW5lczoNCg0KMS4gKipBcHJveGltYWNpw7NuIChMTCk6KiogUmVzdWx0YWRvIGRlIGFwbGljYXIgbG9zIGZpbHRyb3MgXCggSF8xIFwpIHkgXCggVl8xIFwpLiBDb250aWVuZSBsYXMgYmFqYXMgZnJlY3VlbmNpYXMgZGUgbGEgaW1hZ2VuLCBlcyBkZWNpciwgbGEgdmVyc2nDs24gc3Vhdml6YWRhIGRlIGxhIGltYWdlbi4NCiAgIA0KMi4gKipIb3Jpem9udGFsIChMSCk6KiogUmVzdWx0YWRvIGRlIGFwbGljYXIgXCggSF8xIFwpIHkgXCggVl8yIFwpLiBDYXB0dXJhIGRldGFsbGVzIGhvcml6b250YWxlcywgY29tbyBib3JkZXMgdmVydGljYWxlcy4NCg0KMy4gKipWZXJ0aWNhbCAoSEwpOioqIFJlc3VsdGFkbyBkZSBhcGxpY2FyIFwoIEhfMiBcKSB5IFwoIFZfMSBcKS4gQ2FwdHVyYSBkZXRhbGxlcyB2ZXJ0aWNhbGVzLCBjb21vIGJvcmRlcyBob3Jpem9udGFsZXMuDQoNCjQuICoqRGlhZ29uYWwgKEhIKToqKiBSZXN1bHRhZG8gZGUgYXBsaWNhciBcKCBIXzIgXCkgeSBcKCBWXzIgXCkuIENhcHR1cmEgZGV0YWxsZXMgZGlhZ29uYWxlcywgY29tbyBib3JkZXMgaW5jbGluYWRvcyB5IHJ1aWRvLg0KDQpMYSBEV1QgMkQgc2UgZXhwcmVzYSBtYXRlbcOhdGljYW1lbnRlIGNvbW86DQoNCiQkDQpmX3tMTH0oeCwgeSkgPSBIXzEoZih4LCB5KSkgKiBWXzEoZih4LCB5KSkNCiQkDQoNCiQkDQpmX3tMSH0oeCwgeSkgPSBIXzEoZih4LCB5KSkgKiBWXzIoZih4LCB5KSkNCiQkDQoNCiQkDQpmX3tITH0oeCwgeSkgPSBIXzIoZih4LCB5KSkgKiBWXzEoZih4LCB5KSkNCiQkDQoNCiQkDQpmX3tISH0oeCwgeSkgPSBIXzIoZih4LCB5KSkgKiBWXzIoZih4LCB5KSkNCiQkDQoNCioqVW1icmFsIFVuaXZlcnNhbCoqDQoNCkVsIHVtYnJhbCAidW5pdmVyc2FsIiwgcHJvcHVlc3RhIHBvciBEb25vaG8geSBKb2huc3RvbmUuIEVzdGEgZXN0cmF0ZWdpYSBjYWxjdWxhIGVsIHVtYnJhbCBhcGxpY2FkbyBhIGxvcyBjb2VmaWNpZW50ZXMgZGUgd2F2ZWxldCBlbiBmdW5jacOzbiBkZWwgdGFtYcOxbyBkZSBsYSBzZcOxYWwgeSB1bmEgZXN0aW1hY2nDs24gZGVsIG5pdmVsIGRlIHJ1aWRvLiBFc3RlIGVuZm9xdWUgdGllbmUgY29tbyBvYmpldGl2byBlc3RhYmxlY2VyIHVuIHVtYnJhbCBkZSBtYW5lcmEgcXVlIHNlIGVsaW1pbmVuIGxvcyBjb2VmaWNpZW50ZXMgZGUgd2F2ZWxldCBxdWUgY29ycmVzcG9uZGVuIGFsIHJ1aWRvLCBtaWVudHJhcyBzZSBjb25zZXJ2YW4gYXF1ZWxsb3MgcXVlIGNvbnRpZW5lbiBsYSBzZcOxYWwgc2lnbmlmaWNhdGl2YS4NCkxhIGbDs3JtdWxhIGRlbCB1bWJyYWwgInVuaXZlcnNhbCIgZXMgJCQgXHNpZ21hIFxzcXJ0ezIgXGxvZyBuZH0kJCBkb25kZSAkXHNpZ21hJCBlcyB1bmEgZXN0aW1hY2nDs24gZGVsIHJ1aWRvIHkgKm5kKiBlcyBlbCBuw7ptZXJvIGRlIGNvZWZpY2llbnRlcyBlbiBsYSBzdWJiYW5kYSBkZSBkZXRhbGxlcyBjb3JyZXNwb25kaWVudGUgYSB1biBuaXZlbCBkZSBsYSB0cmFuc2Zvcm1hZGEgd2F2ZWxldC4gRXN0ZSB2YWxvciBzZSBvYnRpZW5lIGFjY2VkaWVuZG8gYSBsb3MgY29lZmljaWVudGVzIGRlIGxhIHN1YmJhbmRhIEQgZGUgY2FkYSBuaXZlbC4NCg0KKipVbWJyYWwgRkRSKioNCg0KTGEgdGFzYSBkZSBmYWxzb3MgcG9zaXRpdm9zIChGRFIpIGVzIHVuYSB0w6ljbmljYSBlc3RhZMOtc3RpY2EgdXRpbGl6YWRhIHBhcmEgY29udHJvbGFyIGxhIHRhc2EgZGUgZmFsc29zIHBvc2l0aXZvcyBlbiBlbCBwcm9jZXNvIGRlIHNlbGVjY2nDs24gZGUgY29lZmljaWVudGVzIHJlbGV2YW50ZXMsIHRhbCBjb21vIHNlIGRlc2NyaWJlIGVuIGVsIHRyYWJham8gZGUgQWJyYW1vdmljaCB5IEJlbmphbWluaSAoMTk5NikuIEVuIGVsIGNvbnRleHRvIGRlIGxhIHJlZHVjY2nDs24gZGUgcnVpZG8gbWVkaWFudGUgbGEgVHJhbnNmb3JtYWRhIFdhdmVsZXQsIGVsIG9iamV0aXZvIHByaW5jaXBhbCBkZSBGRFIgZXMgaWRlbnRpZmljYXIgeSBlbGltaW5hciBsb3MgY29lZmljaWVudGVzIGFzb2NpYWRvcyBhbCBydWlkbywgbWllbnRyYXMgc2UgcHJlc2VydmFuIGFxdWVsbG9zIHF1ZSBjb250aWVuZW4gaW5mb3JtYWNpw7NuIHNpZ25pZmljYXRpdmEsIGNvbW8gYm9yZGVzLCB0ZXh0dXJhcyBvIGRldGFsbGVzIGltcG9ydGFudGVzIGRlIGxhIGltYWdlbi4gRXN0byBzZSBsb2dyYSBjYWxjdWxhbmRvLCBwYXJhIGNhZGEgY29lZmljaWVudGUgZGUgbGEgdHJhbnNmb3JtYWRhLCBsYSBwcm9iYWJpbGlkYWQgZGUgcXVlIGRpY2hvIGNvZWZpY2llbnRlIHNlYSB1biBmYWxzbyBwb3NpdGl2bywgZXMgZGVjaXIsIHF1ZSBjb3JyZXNwb25kYSBhIHJ1aWRvIHBlcm8gc2VhIGVycsOzbmVhbWVudGUgY29uc2lkZXJhZG8gcmVsZXZhbnRlLg0KDQoqKk1ldG9kb2xvZ8OtYSoqDQoNClVuYSB2ZXogZXN0aW1hZG8gZWwgbml2ZWwgZGUgcnVpZG8sIHNlIGNhbGN1bGEgcGFyYSBjYWRhIGNvZWZpY2llbnRlIGRlIHdhdmVsZXQgbGEgcHJvYmFiaWxpZGFkIFwoIHAgXCkgZGUgcXVlIGVzZSBjb2VmaWNpZW50ZSBzZWEgcnVpZG8sIHV0aWxpemFuZG8gbGEgZsOzcm11bGE6DQoNClxbDQpwID0gMiBcbGVmdCggMSAtIFxQaGkgXGxlZnQoIFxmcmFje3xkfH17XHRleHR7bm9pc2UubGV2ZWx9fSBccmlnaHQpIFxyaWdodCkNClxdDQoNCmRvbmRlOg0KDQpcKCB8ZHwgXCkgZXMgZWwgdmFsb3IgYWJzb2x1dG8gZGVsIGNvZWZpY2llbnRlIFwoIGQgXCkgZGUgbGEgc3ViYmFuZGEgZGUgZGV0YWxsZXMuDQoNClwoIFx0ZXh0e25vaXNlLmxldmVsfSBcKSBlcyBsYSBkZXN2aWFjacOzbiBlc3TDoW5kYXIgZGUgbG9zIGNvZWZpY2llbnRlcyBkZSBlc2Egc3ViYmFuZGEuDQoNClwoIFxQaGkgXCkgZXMgbGEgZnVuY2nDs24gZGUgZGlzdHJpYnVjacOzbiBhY3VtdWxhZGEgZGUgbGEgbm9ybWFsIGVzdMOhbmRhciwgcXVlIG5vcyBkYSBsYSBwcm9iYWJpbGlkYWQgZGUgcXVlIHVuIHZhbG9yIFwoIGQgXCkgZGFkbyBwcm92ZW5nYSBkZWwgcnVpZG8gKGFzdW1pZG8gY29tbyB1bmEgZGlzdHJpYnVjacOzbiBub3JtYWwpLg0KDQoqKlVtYnJhbGl6YWNpw7NuIHNlZ8O6biBGRFIqKg0KDQpFbCB2YWxvciBjYWxjdWxhZG8gZGUgXCggcCBcKSBkYSB1bmEgbWVkaWRhIGRlIGxhIHByb2JhYmlsaWRhZCBkZSBxdWUgdW4gY29lZmljaWVudGUgc2VhIHJ1aWRvLiBBIGNvbnRpbnVhY2nDs24sIHNlIGRlZmluZSB1biB1bWJyYWwgcGFyYSBsYSB0YXNhIGRlIGRlc2N1YnJpbWllbnRvcyBmYWxzb3MsIGRlbm90YWRhIHBvciBcKCBRIFwpLCBxdWUgZXN0YWJsZWNlIGVsIGzDrW1pdGUgbcOheGltbyBhY2VwdGFibGUgcGFyYSBsYSBwcm9iYWJpbGlkYWQgZGUgZmFsc28gcG9zaXRpdm8uIENvbiB1biBcKCBRID0gMC4wNSBcKSwgZXN0byBzaWduaWZpY2EgcXVlIHNlIHBlcm1pdGlyw6EgdW4gbcOheGltbyBkZWwgNSUgZGUgY29lZmljaWVudGVzIGRlIHJ1aWRvIHF1ZSBzZSBjb25zaWRlcmVuIGVycsOzbmVhbWVudGUgcmVsZXZhbnRlcy4NCg0KTG9zIGNvZWZpY2llbnRlcyBjdXlhIHByb2JhYmlsaWRhZCBcKCBwIFwpIHNlYSBtYXlvciBxdWUgZWwgdW1icmFsIGNhbGN1bGFkbyBzZSBlbGltaW5hbiAoc2UgY29uc2lkZXJhbiBydWlkbyB5IHNlIGVzdGFibGVjZW4gYSBjZXJvKSwgbWllbnRyYXMgcXVlIGFxdWVsbG9zIGNvbiBcKCBwIFwpIG1lbm9yIHF1ZSBlbCB1bWJyYWwgc2UgY29uc2VydmFuLCB5YSBxdWUgc2UgY29uc2lkZXJhbiBzaWduaWZpY2F0aXZvcyAoZXMgZGVjaXIsIHBlcnRlbmVjZW4gYSBsYSBzZcOxYWwgZGUgbGEgaW1hZ2VuKS4NCg0KUGFyYSBhcGxpY2FyIGVsIGFsZ29yaXRtbyBkZSBNYWxsYXQgKElNV0QpLCBlcyBuZWNlc2FyaW8gcXVlIGxhIGltYWdlbiB0ZW5nYSB1bmEgZm9ybWEgY3VhZHJhZGEgY3V5YXMgZGltZW5zaW9uZXMgc2VhbiBwb3RlbmNpYSBkZSBkb3MuIERhZG8gcXVlIG11Y2hhcyBpbcOhZ2VuZXMgbm8gc29uIGN1YWRyYWRhcywgZXMgbmVjZXNhcmlvIGNvbnZlcnRpcmxhcyBhbnRlcyBkZSBhcGxpY2FyIGVsIGFsZ29yaXRtbywgcG9yIGxvIHF1ZSBkZWJlbW9zIHJlYWxpemFyIHVuIHByZS1wcm9jZXNhbWllbnRvIGRlIGxhcyBpbcOhZ2VuZXMuIFNlIGhhbiBlc2NvZ2lkbyAyIG1hbmVyYXMgZGlzdGludGFzIHBhcmEgb2J0ZW5lciBpbcOhZ2VuZXMgY29uIGVsIHRhbWHDsW8gYWRlY3VhZG8uIFBvciB1biBsYWRvLCByZWRpbWVuc2lvbmFyZW1vcyBsYXMgaW3DoWdlbmVzIGNvbiBsYSBmdW5jacOzbiBgcmVzaXplYCwgbG8gcXVlIHBvZHLDrWEgY29ubGxldmFyIHByb2JsZW1hcyBkZSBkaXN0b3JzacOzbiBzaSBsYXMgaW3DoWdlbmVzIGVzdGFiYW4gbGVqb3MgZGUgdGVuZXIgZGltZW5zaW9uZXMgY3VhZHJhZGFzLiBQb3IgZWxsbywgdGFtYmnDqW4gdmFtb3MgYSBlbXBsZWFyIG90cmEgdMOpY25pY2EgeSByZWxsZW5hcmVtb3MgbGFzIG1hdHJpY2VzIGRlIGxhcyBpbcOhZ2VuZXMgY29uIHZhbG9yZXMgZGUgMCDDsyAxIGhhc3RhIGFsY2FuemFyIGxhcyBkaW1lbnNpb25lcyBhZGVjdWFkYXMuIE9ic2VydmFtb3MgbG9zIHByb2JsZW1hcyBxdWUgc3VyZ3VlbiBlbiBjYWRhIGNhc28geSB0cmF0YXJlbW9zIGRlIHNvbHVjaW9uYXJsb3MgZGUgZGlzdGludGFzIG1hbmVyYXMuDQoNCiMjIyBSZWRpbWVuc2lvbmFuZG8gbGFzIGltw6FnZW5lcw0KDQoNCkNvbW8geWEgc2UgaGEgdmlzdG8gcGFyYSBwb2RlciBhcGxpY2FyIGxhIGZ1bmNpw7NuIGBpbXdkKClgZXMgbmVjZXNhcmlvIHBhcnRpciBkZSB1bmEgbWF0cml6IGN1YWRyYWRhIGN1eWFzIGRpbWVuc2lvbmVzIHNlYW4gcG90ZW5jaWEgZGUgZG9zLiBQb3IgZWxsbywgZW4gcHJpbWVyIGx1Z2FyIGNyZWFtb3MgdW5hIGZ1bmNpw7NuIGByZXNpemVfaW13ZCgpYCB0YWwgcXVlIGRhZGEgdW5hIGZvdG8gYnVzY2EgbGEgc3VibWF0cml6IGN1YWRyYWRhIHkgcG90ZW5jaWEgZGUgZG9zIG3DoXMgZ3JhbmRlIHBvc2libGUgeSBhIGNvbnRpbnVhY8OzbiByZWRpbWVuc2lvbmEgbGEgaW1hZ2VuIGEgZGljaGEgc3VibWF0cml6IGN1YWRyYWRhLg0KDQpgYGB7cn0NCiMgUHJlLXByb2Nlc2FtaWVudG8gYWwgYXBsaWNhZG8gZGUgZnVuY2nDs24gaW13ZCAoYWxnb3JpdG1vIGRlIE1hbGxhdCkuDQojIFJlZGltZW5zaW9uYW1pZW50byBkZSBsYSBpbWFnZW4gDQoNCnJlc2l6ZV9pbXdkPC0gZnVuY3Rpb24oZm90byl7DQogIA0KICBpbWcgPC0gYXMuY2ltZyhmb3RvKSAjIFBhc2FyIGEgZm9ybWF0byBJbWFnZXIgcGFyYSBhcGxpY2FyIGZ1bmNpw7NuIHJlc2l6ZQ0KICAgDQogICMgRGltZW5zaW9uZXMgZGUgbGEgZm90bw0KICBkaW1fZm90byA8LSBkaW0oZm90bykNCiAgZmlsYXMgPC0gZGltX2ZvdG9bMV0NCiAgY29sdW1uYXMgPC0gZGltX2ZvdG9bMl0NCiAgDQogIGxhZG9fbWluaW1vIDwtIG1pbihmaWxhcywgY29sdW1uYXMpICAjIFRhbWHDsW8gc3VibWF0cml6IGN1YWRyYWRhIG1hcyBncmFuZGUNCiAgbGFkb19wb3RlbmNpYTIgPC0gMl5mbG9vcihsb2cyKGxhZG9fbWluaW1vKSkgIyBUYW1hw7FvIHN1Ym1hdHJpeiBjdWFkcmFkYSBwb3RlbmNpYSBkZSBkb3MgbWFzIGdyYW5kZQ0KICANCiAgZm90b19yZXNpemVkIDwtcmVzaXplKGZvdG8sIHcgPSBsYWRvX3BvdGVuY2lhMiwgaCA9IGxhZG9fcG90ZW5jaWEyKSAjIFJlZGltZW5zaW9uYWRvIGRlIGxhIGltYWdlbg0KDQpyZXR1cm4oZm90b19yZXNpemVkKQ0KfQ0KYGBgDQoNClBvciBvdHJvIGxhZG8sIGNyZWFtb3MgdW5hIGZ1bmNpw7NuIHBhcmEgZWwgcG9zdC1wcm9jZXNhbWllbnRvIGRlIGxhcyBpbcOhZ2VuZXMgdHJhcyBsYSBlbGltaW5pY2nDs24gZGUgcnVpZG8uIFF1ZXJlbW9zIGRldm9sdmVybGFzIGEgc3UgdGFtYcOxbyBvcmlnaW5hbCBjb24gZWwgb2JqZXRpdm8gZGUgY29tcGFyYXIgY29uIGxhcyBpbcOhZ2VuZXMgaW5pY2lhbGVzLg0KDQpgYGB7cn0NCiMgUG9zdC1wcm9jZXNhbWllbnRvIGRlIGxhIGltYWdlbjoNCiMgUmVkaW1lbnNpb25hbWllbnRvIGRlIGxhIGltYWdlbiBhIHN1IHRhbWHDsW8gb3JpZ2luYWwuDQoNCnJlc2l6ZV9pbXdkX3RvX29yaWdpbmFsPC0gZnVuY3Rpb24oaW1hZ2VfcmVkaW1lbnNpb25hZGEsIG5vbWJyZV9mb3RvKXsNCiAgDQogICNpbWcgPC0gYXMuY2ltZyhpbWFnZV9yZWRpbWVuc2lvbmFkYSkNCiAgZm90byA8LSBpbWFnZXNbW25vbWJyZV9mb3RvXV0NCiAgDQogICMgRGltZW5zaW9uZXMgZGUgbGEgZm90bw0KICBkaW1fZm90byA8LSBkaW0oZm90bykNCiAgZmlsYXMgPC0gZGltX2ZvdG9bMl0NCiAgY29sdW1uYXMgPC0gZGltX2ZvdG9bMV0NCiAgDQogICMgUmVkaW1lbnNpb25hZG8gZGUgbGEgaW1hZ2VuDQogIGZvdG9fcmVzaXplZCA8LUVCSW1hZ2U6OnJlc2l6ZShpbWFnZV9yZWRpbWVuc2lvbmFkYSwgdyA9IGNvbHVtbmFzLCBoID0gZmlsYXMpIA0KIA0KDQpyZXR1cm4oZm90b19yZXNpemVkKQ0KfQ0KYGBgDQoNCkNvbWVuemFtb3MgZ2VuZXJhbmRvIHVuYSBmdW5jacOzbiBgcHJvY2VzYXJfaW1hZ2VuX3dhdmVsZXRgIGNvbiBwYXLDoW1ldHJvcyBmb3RvLCB0aXBvIHkgcG9saWN5LiBFc3RhIGZ1bmNpw7NuIHJlYWxpemEgZW4gcHJpbWVyIGx1Z2FyIGxhIHRyYW5zZm9ybWFkYSB3YXZlbGV0IGEgY2FkYSB1bm8gZGUgbG9zIHRyZXMgY2FuYWxlcyBkZSB1bmEgaW1hZ2VuLiBBIGNvbnRpbnVhY2nDs24sIHNlIHJlYWxpemEgZWwgdGhyZXNob2xkaW5nIGNvbiBsYSBmdW5jacOzbiB0aHJlc2hvbGQsIHB1ZGllbmRvIHZhcmlhciBkZSBlbCB0aXBvIGRlICJoYXJkIiBhICJzb2Z0IiB5IGVsIHBhcsOhbWV0cm8gcG9saWN5IChtb2RpZmljYW5kbyBhZGVjdWFkYW1lbnRlIGxvcyBwYXLDoW1ldHJvcyBuZWNlc2FyaW9zIGVuIGxhIGZ1bmNpw7NuIHRocmVzaG9sZCBlbiBlc3RlIMO6bHRpbW8gY2FzbykuIFVuYSB2ZXogcmVhbGl6YWRhIGxhIGVsaW1pbmFjacOzbiBkZSBydWlkbywgc2UgYXBsaWNhIGxhIHRyYXNuZm9ybWFkYSB3YXZlbGV0IGludmVyc2EgYGltd3JgIHBhcmEgcG9yIMO6bHRpbW8gcmVjb25zdHJ1aXIgbGEgaW1hZ2VuIGEgcGFydGlyIGRlIGxvcyB0cmVzIGNhbmFsZXMuDQoNCmBgYHtyfQ0KcHJvY2VzYXJfaW1hZ2VuX3dhdmVsZXQgPC0gZnVuY3Rpb24oZm90bywgdGlwbyA9ICJoYXJkIiwgcG9saWN5ID0gInVuaXZlcnNhbCIpIHsNCiAgIyAxLiBSZWFsaXphbW9zIGxhIHRyYW5zZm9ybWFkYSB3YXZlbGV0IGEgY2FkYSBjYW5hbA0KICBsd2QgPC0gbGFwcGx5KDE6MywgZnVuY3Rpb24oY2FuYWwpIHsNCiAgICBpbXdkKGZvdG9bLCxjYW5hbF0pICANCiAgfSkNCiAgDQogICMgMi4gQXBsaWNhbW9zIGVsIHVtYnJhbCBhIGxvcyBjb2VmaWNpZW50ZXMgZGUgbGEgdHJhbnNmb3JtYWRhIHdhdmVsZXQNCiAgbHdkX3RocmVzaG9sZCA8LSBsYXBwbHkobHdkLCBmdW5jdGlvbihjYW5hbF93ZCkgew0KICAgIG5pdmVsZXMgPC0gY2FuYWxfd2QkbmxldmVscw0KICAgIHdhdmV0aHJlc2g6OnRocmVzaG9sZChjYW5hbF93ZCwgbGV2ZWxzID0gMzoobml2ZWxlcy0xKSwgdHlwZSA9IHRpcG8sIHBvbGljeSA9IHBvbGljeSxieV9sZXZlbD1UUlVFLGNvbXByZXNzaW9uPUZBTFNFKQ0KICB9KQ0KICAjIDMuIEFwbGljYW1vcyBsYSB0cmFuc2Zvcm1hZGEgd2F2ZWxldCBpbnZlcnNhIGEgY2FkYSBjYW5hbCB1bWJyYWxpemFkbw0KICBpbHdkIDwtIGxhcHBseShsd2RfdGhyZXNob2xkLCBmdW5jdGlvbihjYW5hbF91bWJyYWxpemFkbykgew0KICAgIHdhdmV0aHJlc2g6Omltd3IoY2FuYWxfdW1icmFsaXphZG8pICAjIFRyYW5zZm9ybWFkYSB3YXZlbGV0IGludmVyc2ENCiAgfSkNCiAgDQogICMgNC4gUmVjb25zdHJ1aXIgbGEgaW1hZ2VuIGNvbWJpbmFuZG8gbG9zIHRyZXMgY2FuYWxlcyBwcm9jZXNhZG9zDQogIGltYWdlbl9yZWNvbnN0cnVpZGEgPC0gYWJpbmQ6OmFiaW5kKGlsd2RbWzFdXSwgaWx3ZFtbMl1dLCBpbHdkW1szXV0sIGFsb25nID0gMykNCiAgICBpbWFnZW4gPC0gSW1hZ2UoaW1hZ2VuX3JlY29uc3RydWlkYSwgY29sb3Jtb2RlID0gJ0NvbG9yJykNCiAgDQogIHJldHVybihpbWFnZW4pDQp9DQpgYGANCg0KDQpQYXJhIGNvbWVuemFyIGVsIGFuw6FsaXNpcywgdmFtb3MgYSBlbXBsZWFyIGRvcyBpbcOhZ2VuZXMgbXV5IHBhcmVjaWRhcyAoaW3DoWdlbmVzIDQgeSA1KS4gVW5hIGRlIGVsbGFzIHRpZW5lIG11eSBhbHRhIHJlc29sdWNpw7NuIG1pZW50cmFzIHF1ZSBsYSBzZWd1bmRhIGN1ZW50YSBjb24gdW5hIGNhbGlkYWQgbXVjaG8gbWVub3IuIEVsIG9iamV0aXZvIGVzIGRldGVybWluYXIgc2kgbGEgcmVzb2x1Y2nDs24gZGUgbGEgaW1hZ2VuIGFmZWN0YSBhIGxhIGhvcmEgZGUgZWxpbWluYXIgcnVpZG8gZGUgZXN0YS4gVmFtb3MgYSBwcm9iYXIgY29uIGVsIHByaW1lciB0aXBvIGRlIHJ1aWRvLCBydWlkbyBnYXVzc2lhbm8uDQoNCmBgYHtyfQ0KIyBBw7FhZGltb3MgcnVpZG8gZ2F1c3NpYW5vIGEgbGFzIGltw6FnZW5lcyBjb24gc2QgPSAwLjMNCmltYWdlXzRfZ2F1c3NpYW5fbm9pc2UgPC0gYWRkX25vaXNlX3RvX2ltYWdlKCI0IiwgImdhdXNzaWFuIiwgbGlzdChzdGRfZGV2ID0gMC4zKSkNCmltYWdlXzVfZ2F1c3NpYW5fbm9pc2UgPC0gYWRkX25vaXNlX3RvX2ltYWdlKCI1IiwgImdhdXNzaWFuIiwgbGlzdChzdGRfZGV2ID0gMC4zKSkNCg0KIyBIYWNlbW9zIHVuYSBsaXN0YSBjb24gbGFzIGltw6FnZW5lcyBjb24gcnVpZG8gYSByZWRpbWVuc2lvbmFyDQppbWFnZXNfZm9yX2ltd2RfY3V0XzEgPC0gbGlzdChpbWFnZV80X2dhdXNzaWFuX25vaXNlLCBpbWFnZV81X2dhdXNzaWFuX25vaXNlKQ0KbmFtZXMoaW1hZ2VzX2Zvcl9pbXdkX2N1dF8xKSA8LSBjKCc0IE5vaXNlOiBnYXVzc2lhbicsICc1IE5vaXNlOiBnYXVzc2lhbicpDQoNCiMgRWxpbWluYW1vcyB2YXJpYWJsZXMgaW5uZWNlc2FyaWFzDQpybShpbWFnZV80X2dhdXNzaWFuX25vaXNlKQ0Kcm0oaW1hZ2VfNV9nYXVzc2lhbl9ub2lzZSkNCmBgYA0KDQpBcGxpY2Ftb3MgbGEgZnVuY2nDs24gYHJlc2l6ZV9pbXdkYCBjcmVhZGEgYW50ZXJpb3JtZW50ZSBwYXJhIG9idGVuZXIgdW5hIG1hdHJpeiBjb24gbGFzIGRpbWVuc2lvbmVzIG5lY2VzYXJpYXMgcGFyYSBhcGxpY2FyIGxhIHRyYW5zZm9ybWFkYSB3YXZlbGV0Lg0KYGBge3J9DQppbWFnZXNfcmVjb3J0YWRhcyA8LSBsYXBwbHkoaW1hZ2VzX2Zvcl9pbXdkX2N1dF8xLCByZXNpemVfaW13ZCkNCmBgYA0KDQpVbmEgdmV6IHRlbmVtb3MgbGFzIGltw6FnZW5lcyBjb24gcnVpZG8gZ2VuZXJhZGFzIHkgcmVkaW1lbnNpb25hZGFzIGFkZWN1YWRhbWVudGUsIHBvZGVtb3MgYXBsaWNhciBsYSBmdW5jacOzbiBgaW13ZGAgYSBjYWRhIHVubyBkZSBsb3MgdHJlcyBjYW5hbGVzIChSLCBHIHkgQikuIEVtcGxlYW1vcyBsYSBmdW5jacOzbiBgcHJvY2VzYXJfaW1hZ2VuX3dhdmVsZXRgIHF1ZSBkZXZ1ZWx2ZSBsYXMgaW3DoWdlbmVzIHJlY29uc3RydWlkYXMgZGVzcHXDqXMgZGVsIHRocmVzaG9sZGluZy4gUGFyYSBydWlkbyBnYXVzc2lhbm8geSBwYXJhIGNvbWVuemFyLCB2YW1vcyBhIGVsZWdpciBsb3MgcGFyw6FtZXRyb3MgcG9yIGRlZmVjdG8gZGUgbGEgZnVuY2nDs24gcGFyYSBlbCB0aHJlc2hvbGRpbmcuDQoNCmBgYHtyfQ0KaW1hZ2VzX3Npbl9ydWlkb19nYXVzc2lhbm8gPC0gbGFwcGx5KGltYWdlc19yZWNvcnRhZGFzLCBwcm9jZXNhcl9pbWFnZW5fd2F2ZWxldCkNCmBgYA0KRmluYWxtZW50ZSwgdmlzdWFsaXphbW9zIGxvcyByZXN1bHRhZG9zLiBQcmltZXJhbWVudGUsIG9ic2VydmFtb3MgbGFzIGltw6FnZW5lcyByZWRpbWVuc2lvbmFkYXMgY29uIHJ1aWRvIHkgbGEgaW1hZ2VuIG9idGVuaWRhIHRyYXMgZWwgdXNvIGRlbCBtw6l0b2RvIGRlIHRocmVzaG9sZGluZyBwYXJhIGxhIGVsaW1pbmFjacOzbiBkZSBlc3RlLiBPYnNlcnZhbW9zIHVuYSBwcmluY2lwYWwgZGlmZXJlbmNpYSBlbnRyZSBhbWJhczogbGEgZm90byBxdWUgY29udGFiYSBjb24gbWVub3IgcmVzb2x1Y2nDs24gcHJlc2VudGEgdGFtYmnDqW4gZWwgcGVvciByZXN1bHRhZG8uIEF1bnF1ZSBlbCBydWlkbyBoYXlhIHNpZG8gZWxpbWluYWRvbSwgc3VzIGJvcmRlcyBlc3TDoW4gbcOhcyBkaWZ1bWluYWRvcyB5IHRpZW5lIG11eSBiYWphIGNhbGlkYWQuDQoNCmBgYHtyfQ0KcGFyKG1mcm93ID0gYygyLCAyKSwgY2V4ID0gMC41KQ0KDQpkaXNwbGF5KEltYWdlKGltYWdlc19yZWNvcnRhZGFzW1sxXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQ0KdGl0bGUoJ0ltYWdlbiA0IGNvbiBydWlkbycpDQpkaXNwbGF5KEltYWdlKGltYWdlc19zaW5fcnVpZG9fZ2F1c3NpYW5vW1sxXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQ0KdGl0bGUoJ0ltYWdlbiA0IHNpbiBydWlkbycpDQoNCmRpc3BsYXkoSW1hZ2UoaW1hZ2VzX3JlY29ydGFkYXNbWzJdXSwgY29sb3Jtb2RlID0gJ0NvbG9yJyksIG1ldGhvZD0ncicpDQp0aXRsZSgnSW1hZ2VuIDUgY29uIHJ1aWRvJykNCmRpc3BsYXkoSW1hZ2UoaW1hZ2VzX3Npbl9ydWlkb19nYXVzc2lhbm9bWzJdXSwgY29sb3Jtb2RlID0gJ0NvbG9yJyksIG1ldGhvZD0ncicpDQp0aXRsZSgnSW1hZ2VuIDUgc2luIHJ1aWRvJykNCmBgYA0KRW4gc2VndW5kbyBsdWdhciwgdmFtb3MgYSB2aXN1YWxpemFyIGxhcyBpbcOhZ2VuZXMgb3JpZ2luYWxlcyB5IGxhcyBpbcOhZ2VuZXMgc2luIHJ1aWRvIHJlZGltZW5zaW9uYWRhcyBhIHN1IHRhbWHDsW8gb3JpZ2luYWwsIHVzYW5kbyBsYSBmdW5jacOzbiBgcmVzaXplX2ltd2RfdG9fb3JpZ2luYWxgLg0KDQpgYGB7cn0NCmltYWdlc19zaW5fcnVpZG8gPC0gTWFwKHJlc2l6ZV9pbXdkX3RvX29yaWdpbmFsLCBpbWFnZXNfc2luX3J1aWRvX2dhdXNzaWFubywgYygiNCIsICI1IikgKQ0KYGBgDQoNCg0KYGBge3J9DQpwYXIobWZyb3cgPSBjKDIsIDIpLCBjZXggPSAwLjUpDQoNCmRpc3BsYXkoSW1hZ2UoaW1hZ2VzW1s0XV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQ0KdGl0bGUoJ0ltYWdlbiA0IG9yaWdpbmFsJykNCmRpc3BsYXkoSW1hZ2UoaW1hZ2VzX3Npbl9ydWlkb1tbMV1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykNCnRpdGxlKCdJbWFnZW4gNCBzaW4gcnVpZG8nKQ0KDQpkaXNwbGF5KEltYWdlKGltYWdlc1tbNV1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykNCnRpdGxlKCdJbWFnZW4gNSBvcmlnaW5hbCcpDQpkaXNwbGF5KEltYWdlKGltYWdlc19zaW5fcnVpZG9bWzJdXSwgY29sb3Jtb2RlID0gJ0NvbG9yJyksIG1ldGhvZD0ncicpDQp0aXRsZSgnSW1hZ2VuIDUgc2luIHJ1aWRvJykNCmBgYA0KVmVtb3MgcXVlIGVmZWN0aXZhbWVudGUsIGxhIGltYWdlbiBxdWUgb3JpZ2luYWxlbWVudGUgY29udGFiYSBjb24gdW4gbsO6bWVybyBkZSBww614ZWxlcyBtdWNobyBtZW5vciwgbGEgaW1hZ2VuIDUsIHByZXNlbnRhIHVuYSBncmFuIGRpc3RvcnNpw7NuIHRyYXMgbGEgZWxpbWluYWNpw7NuIGRlIHJ1aWRvLg0KDQpgYGB7cn0NCnJtKGltYWdlc19mb3JfaW13ZF9jdXRfMSxpbWFnZXNfc2luX3J1aWRvX2dhdXNzaWFubykNCmBgYA0KDQoNCkEgY29udGludWFjacOzbiB2YW1vcyBhIGNvbXByb2JhciBxdWUgZXMgbG8gcXVlIG9jdXJyZSBjdWFuZG8gYcOxYWRpbW9zIHJ1aWRvIHNpbnTDqXRpY28gc2ludXNvaWRhbCB5IHNpIGxhIGZyZWN1ZW5jaWEgZGUgZXN0ZSBhZmVjdGEgYWwgcmVzdWx0YWRvIGRlIGxhIGVsaW1pbmFjacOzbiBkZSBydWlkby4gVHJhYmFqYXJlbW9zIGNvbiB1bmEgw7puaWNhIGZvdG9ncmFmw61hOiBsYSBpbWFnZW4gMS4NCg0KYGBge3J9DQojIEHDsWFkaW1vcyBydWlkbyBzaW51c29pZGFsIGEgbGFzIGltw6FnZW5lcyBjb24gc2QgPSAwLjMNCmltYWdlXzFfc2lub3N1aWRhbF9oaWdoIDwtIGFkZF9ub2lzZV90b19pbWFnZSgiMSIsICJzaW51c29pZGFsX2hpZ2giLCBsaXN0KGZyZXF1ZW5jeSA9IDUwLCBhbXBsaXR1ZGUgPSAwLjMpKQ0KaW1hZ2VfMV9zaW5vc3VpZGFsX2xvdyA8LSBhZGRfbm9pc2VfdG9faW1hZ2UoIjIiLCAic2ludXNvaWRhbF9sb3ciLCBsaXN0KGZyZXF1ZW5jeSA9IDUsIGFtcGxpdHVkZSA9IDAuMykpDQoNCiMgSGFjZW1vcyB1bmEgbGlzdGEgY29uIGxhcyBpbcOhZ2VuZXMgY29uIHJ1aWRvIGEgcmVkaW1lbnNpb25hcg0KaW1hZ2VzX2Zvcl9pbXdkX2N1dF8yIDwtIGxpc3QoaW1hZ2VfMV9zaW5vc3VpZGFsX2hpZ2ggLCBpbWFnZV8xX3Npbm9zdWlkYWxfbG93ICkNCm5hbWVzKGltYWdlc19mb3JfaW13ZF9jdXRfMikgPC0gYygnMSBOb2lzZTogc2ludXNvaWRhbCBoaWdoJywgJzEgTm9pc2U6IHNpbnVzb2lkYWwgbG93JykNCg0KIyBFbGltaW5hbW9zIHZhcmlhYmxlcyBpbm5lY2VzYXJpYXMNCnJtKGltYWdlXzFfc2lub3N1aWRhbF9oaWdoKQ0Kcm0oaW1hZ2VfMV9zaW5vc3VpZGFsX2xvdykNCmBgYA0KDQpgYGB7cn0NCmltYWdlc19yZWNvcnRhZGFzIDwtIGxhcHBseShpbWFnZXNfZm9yX2ltd2RfY3V0XzIsIHJlc2l6ZV9pbXdkKQ0KaW1hZ2VzX3Npbl9ydWlkb19zaW51c29pZGFsPC0gbGFwcGx5KGltYWdlc19yZWNvcnRhZGFzLCBwcm9jZXNhcl9pbWFnZW5fd2F2ZWxldCkNCmBgYA0KRXN0YSBjbGFybyBxdWUgbG9zIHBhcsOhbWV0cm9zIHBvciBkZWZlY3RvIGRlIGxhIGZ1bmNpw7NuIHRocmVzaG9sZCBubyBzb24gY2FwYWNlcyBkZSBlbGltaW5hciBlbCBydWlkbyBkZSB0aXBvIHNpbnVzb2lkYWwgZGUgbGEgbWFuZXJhIGVuIHF1ZSBzaSBsbyBlcmEgY29uIGVsIHJ1aWRvIGRlIHRpcG8gR2F1c3NpYW5vLCB1biB0aXBvIGRlIHJ1aWRvIGFsZWF0b3JpbywgYWwgY29udHJhcmlvIHF1ZSBlbCBzaW51c29pZGFsLCBxdWUgZXMgdW5hIHNlw7FhbCBwZXJpw7NkaWNhLg0KYGBge3J9DQpwYXIobWZyb3cgPSBjKDIsIDIpLCBjZXggPSAwLjUpDQoNCmRpc3BsYXkoSW1hZ2UoaW1hZ2VzX3JlY29ydGFkYXNbWzFdXSwgY29sb3Jtb2RlID0gJ0NvbG9yJyksIG1ldGhvZD0ncicpDQp0aXRsZSgnSW1hZ2VuIDEgY29uIHJ1aWRvIGRlIGZyZWN1ZW5jaWEgYWx0YScpDQpkaXNwbGF5KEltYWdlKGltYWdlc19zaW5fcnVpZG9fc2ludXNvaWRhbFtbMV1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykNCnRpdGxlKCdJbWFnZW4gMSBzaW4gcnVpZG8nKQ0KDQpkaXNwbGF5KEltYWdlKGltYWdlc19yZWNvcnRhZGFzW1syXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQ0KdGl0bGUoJ0ltYWdlbiAxIGNvbiBydWlkbyBkZSBmcmVjdWVuY2lhIGJhamEnKQ0KZGlzcGxheShJbWFnZShpbWFnZXNfc2luX3J1aWRvX3NpbnVzb2lkYWxbWzJdXSwgY29sb3Jtb2RlID0gJ0NvbG9yJyksIG1ldGhvZD0ncicpDQp0aXRsZSgnSW1hZ2VuIDEgc2luIHJ1aWRvJykNCmBgYA0KVmFtb3MgYSB2YXJpYXIgcGFyw6FtZXRyb3MgZGUgbGEgZnVuY2nDs24gdGhyZXNob2xkIHBhcmEgaW50ZW50YXIgcXVpdGFyIGVzdGUgcnVpZG8gZGUgbWFuZXJhIG3DoXMgbWFudWFsLiBFbiBwcmltZXIgbHVnYXIsIGNhbWJpYW1vcyBlbCBuw7ptZXJvIGRlIG5pdmVsZXMgYWwgcXVlIGFwbGljYW1vcyBlbCB1bWJyYWwsIHBhcmEgaW5jbHVpcmxvcyBhIHRvZG9zLiBDYW1iaWFtb3MgcG9saWN5IGEgIm1hbnVhbCIgeSB2YXJpYW1vcyBlbCB2YWxvciBkZWwgdW1icmFsIGRlIGZvcm1hIG1hbnVhbCBoYXN0YSBlbmNvbnRyYXIgdW5vIHF1ZSBzZWEgc2F0aXNmYWN0b3Jpby4gDQpgYGB7cn0NCnByb2Nlc2FyX2ltYWdlbl93YXZlbGV0X3NpbnVzb2lkYWwgPC0gZnVuY3Rpb24oZm90bywgdGlwbyA9ICJoYXJkIiwgcG9saWN5ID0gInVuaXZlcnNhbCIpIHsNCiAgIyAxLiBSZWFsaXphbW9zIGxhIHRyYW5zZm9ybWFkYSB3YXZlbGV0IGEgY2FkYSBjYW5hbA0KICBsd2QgPC0gbGFwcGx5KDE6MywgZnVuY3Rpb24oY2FuYWwpIHsNCiAgICBpbXdkKGZvdG9bLCxjYW5hbF0pICANCiAgfSkNCiAgDQogICMgMi4gQXBsaWNhbW9zIGVsIHVtYnJhbCBhIGxvcyBjb2VmaWNpZW50ZXMgZGUgbGEgdHJhbnNmb3JtYWRhIHdhdmVsZXQNCiAgbHdkX3RocmVzaG9sZCA8LSBsYXBwbHkobHdkLCBmdW5jdGlvbihjYW5hbF93ZCkgew0KICAgIG5pdmVsZXMgPC0gY2FuYWxfd2QkbmxldmVscw0KICAgIHdhdmV0aHJlc2g6OnRocmVzaG9sZChjYW5hbF93ZCwgbGV2ZWxzID0gMToobml2ZWxlcy0xKSwgdHlwZSA9IHRpcG8sIHBvbGljeSA9IHBvbGljeSxieV9sZXZlbD1UUlVFLGNvbXByZXNzaW9uPUZBTFNFLCB2YWx1ZSA9IDQpDQogIH0pDQogICMgMy4gQXBsaWNhbW9zIGxhIHRyYW5zZm9ybWFkYSB3YXZlbGV0IGludmVyc2EgYSBjYWRhIGNhbmFsIHVtYnJhbGl6YWRvDQogIGlsd2QgPC0gbGFwcGx5KGx3ZF90aHJlc2hvbGQsIGZ1bmN0aW9uKGNhbmFsX3VtYnJhbGl6YWRvKSB7DQogICAgd2F2ZXRocmVzaDo6aW13cihjYW5hbF91bWJyYWxpemFkbykgICMgVHJhbnNmb3JtYWRhIHdhdmVsZXQgaW52ZXJzYQ0KICB9KQ0KICANCiAgIyA0LiBSZWNvbnN0cnVpciBsYSBpbWFnZW4gY29tYmluYW5kbyBsb3MgdHJlcyBjYW5hbGVzIHByb2Nlc2Fkb3MNCiAgaW1hZ2VuX3JlY29uc3RydWlkYSA8LSBhYmluZDo6YWJpbmQoaWx3ZFtbMV1dLCBpbHdkW1syXV0sIGlsd2RbWzNdXSwgYWxvbmcgPSAzKQ0KICAgIGltYWdlbiA8LSBJbWFnZShpbWFnZW5fcmVjb25zdHJ1aWRhLCBjb2xvcm1vZGUgPSAnQ29sb3InKQ0KICANCiAgcmV0dXJuKGltYWdlbikNCn0NCmBgYA0KDQoNCmBgYHtyfQ0KaW1hZ2VzX3Npbl9ydWlkb19zaW51c29pZGFsPC0gbGFwcGx5KGltYWdlc19yZWNvcnRhZGFzLCBwcm9jZXNhcl9pbWFnZW5fd2F2ZWxldF9zaW51c29pZGFsLCB0aXBvID0ic29mdCIsIHBvbGljeSA9ICJtYW51YWwiKQ0KYGBgDQoNCkNvbiB1biB2YWxvciBkZSB1bWJyYWwgZGUgNCB5IHZhcmlhbmRvIGVsIHRpcG8gYSAic29mdCIsIG9ic2VydmFtb3MgcXVlIGhlbW9zIGNvbnNlZ3VpZG8gZWxpbWluYXIgZWwgcnVpZG8gc2ludXNvaWRhbCBkZSBhbHRhIGZyZWN1ZW5jaWEsIHBlcm8gcGFnYW5kbyB1biBwcmVjaW8gbXV5IGFsdG86IGxvcyBib3JkZXMgZGUgbGEgaW1hZ2VuIHNlIGRpc29yc2lvbmFuIGNvbXBsZXRhbWVudGUgeSB0ZW5lbW9zIHVuYSBtdXkgYmFqYSByZXNvbHVjacOzbi4gUG9yIG90cm8gbGFkbywgZWwgcnVpZG8gZGUgZnJlY3VlbmNpYSBiYWphLCBhdW5xdWUgaGEgZGlzbWludWlkbywgY2xhcmFtZW50ZSBzaWd1ZSBwcmVzZW50ZSBlbiBsYSBpbWFnZW4uIEVsIHVtYnJhbCBuZWNlc2FyaW8gcGFyYSBlbGltaW5hcmxvIGNvbiBlc3RlIG3DqXRvZG8gZXMgdGFuIGFsdG8gcXVlIGRpc3RvcnNpb25hcsOtYSBsYSBpbWFnZW4gY2FzaSBwb3IgY29tcGxldG8uDQpgYGB7cn0NCnBhcihtZnJvdyA9IGMoMiwgMiksIGNleCA9IDAuNSkNCg0KZGlzcGxheShJbWFnZShpbWFnZXNfcmVjb3J0YWRhc1tbMV1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykNCnRpdGxlKCdJbWFnZW4gMSBjb24gcnVpZG8gZGUgZnJlY3VlbmNpYSBhbHRhJykNCmRpc3BsYXkoSW1hZ2UoaW1hZ2VzX3Npbl9ydWlkb19zaW51c29pZGFsW1sxXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQ0KdGl0bGUoJ0ltYWdlbiAxIHNpbiBydWlkbycpDQoNCmRpc3BsYXkoSW1hZ2UoaW1hZ2VzX3JlY29ydGFkYXNbWzJdXSwgY29sb3Jtb2RlID0gJ0NvbG9yJyksIG1ldGhvZD0ncicpDQp0aXRsZSgnSW1hZ2VuIDEgY29uIHJ1aWRvIGRlIGZyZWN1ZW5jaWEgYmFqYScpDQpkaXNwbGF5KEltYWdlKGltYWdlc19zaW5fcnVpZG9fc2ludXNvaWRhbFtbMl1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykNCnRpdGxlKCdJbWFnZW4gMSBzaW4gcnVpZG8nKQ0KYGBgDQoNClByb2JhbW9zIG90cm9zIHRpcG9zIGRlIHJ1aWRvOiBnYW1tYSwgc2FsdCBhbmQgcGVwcGVyIHkgcnVpZG8gdW5pZm9ybWUgZW4gbGFzIGltw6FnZW5lcyAyIHkgMy4gDQpgYGB7cn0NCiMgQcOxYWRpbW9zIHJ1aWRvIGEgaW3DoWdlbmVzDQppbWFnZV8yX3NhbHRfcGVwcGVyIDwtIGFkZF9ub2lzZV90b19pbWFnZSgiMiIsICJzYWx0X3BlcHBlciIsIGxpc3QoZXBzaWxvbiA9IDAuMSkpDQppbWFnZV8zX2dhbW1hIDwtIGFkZF9ub2lzZV90b19pbWFnZSgiMyIsICJnYW1tYSIsIGxpc3QobG9va3MgPSAyKSkNCmltYWdlXzNfdW5pZm9ybSA8LSBhZGRfbm9pc2VfdG9faW1hZ2UoIjMiLCAidW5pZm9ybV9tdWx0aXBsaWNhdGl2ZSIsIGxpc3QobG9va3MgPSAyKSkNCg0KDQojIEhhY2Vtb3MgdW5hIGxpc3RhIGNvbiBsYXMgaW3DoWdlbmVzIGNvbiBydWlkbyBhIHJlY29ydGFyDQppbWFnZXNfZm9yX2ltd2RfY3V0XzMgPC0gbGlzdCggaW1hZ2VfMl9zYWx0X3BlcHBlciwgaW1hZ2VfM19nYW1tYSwgaW1hZ2VfM191bmlmb3JtKQ0KbmFtZXMoaW1hZ2VzX2Zvcl9pbXdkX2N1dF8zKSA8LSBjKCcyIE5vaXNlOiBzYWx0IGFuZCBwZXBwZXInLCAnMyBOb2lzZTogZ2FtbWEnLCczIE5vaXNlOiB1bmlmb3JtJykNCg0KIyBFbGltaW5hbW9zIHZhcmlhYmxlcyBpbm5lY2VzYXJpYXMNCnJtKGltYWdlXzJfc2FsdF9wZXBwZXIsIGltYWdlXzNfZ2FtbWEsIGltYWdlXzNfdW5pZm9ybSkNCg0KYGBgDQpTdWd1aWVuZG8gZWwgbWlzbW8gcHJvY2VkaW1pZW50bywgcmVkaW1lbnNpb25hbW9zIGxhcyBpbcOhZ2VuZXMgeSB5IHJlYWxpemFtb3MgbGFzIHRyYW5zZm9ybWFkYXMgd2F2ZWxldCB5IGVsIHRocmVzaG9sZGluZyBjb24gbGEgZnVuY2nDs24gYHByb2Nlc2FyX2ltYWdlbl93YXZlbGV0YC4NCmBgYHtyfQ0KaW1hZ2VzX3JlY29ydGFkYXMgPC0gbGFwcGx5KGltYWdlc19mb3JfaW13ZF9jdXRfMywgcmVzaXplX2ltd2QpDQppbWFnZXNfc2luX3J1aWRvczwtIGxhcHBseShpbWFnZXNfcmVjb3J0YWRhcywgcHJvY2VzYXJfaW1hZ2VuX3dhdmVsZXQpDQpgYGANClZpc3VhbGl6YW1vcyBsb3MgcmVzdWx0YWRvcy4gUGFyZWNlIHF1ZSBlbCBhbGdvcml0bW8gZnVuY2lvbmEgY29ycmVjdGFtZW50ZSBwYXJhIGxvcyB0cmVzIHRpcG9zIGRlIHJ1aWRvLg0KYGBge3J9DQpwYXIobWZyb3cgPSBjKDMsIDIpLCBjZXggPSAwLjUpDQoNCmRpc3BsYXkoSW1hZ2UoaW1hZ2VzX3JlY29ydGFkYXNbWzFdXSwgY29sb3Jtb2RlID0gJ0NvbG9yJyksIG1ldGhvZD0ncicpDQp0aXRsZSgnSW1hZ2VuIDIgY29uIHNhbHQgYW5kIHBlcHBlcicpDQpkaXNwbGF5KEltYWdlKGltYWdlc19zaW5fcnVpZG9zW1sxXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQ0KdGl0bGUoJ0ltYWdlbiAyIHNpbiBydWlkbycpDQoNCmRpc3BsYXkoSW1hZ2UoaW1hZ2VzX3JlY29ydGFkYXNbWzJdXSwgY29sb3Jtb2RlID0gJ0NvbG9yJyksIG1ldGhvZD0ncicpDQp0aXRsZSgnSW1hZ2VuIDMgY29uIHJ1aWRvIGdhbW1hJykNCmRpc3BsYXkoSW1hZ2UoaW1hZ2VzX3Npbl9ydWlkb3NbWzJdXSwgY29sb3Jtb2RlID0gJ0NvbG9yJyksIG1ldGhvZD0ncicpDQp0aXRsZSgnSW1hZ2VuIDMgc2luIHJ1aWRvJykNCg0KZGlzcGxheShJbWFnZShpbWFnZXNfcmVjb3J0YWRhc1tbM11dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykNCnRpdGxlKCdJbWFnZW4gMyBjb24gcnVpZG8gdW5pZm9ybWUnKQ0KZGlzcGxheShJbWFnZShpbWFnZXNfc2luX3J1aWRvc1tbM11dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykNCnRpdGxlKCdJbWFnZW4gMyBzaW4gcnVpZG8nKQ0KYGBgDQpSZWRpbWVuc2lvbmFtb3MgbGFzIGltw6FnZW5lcyBvYnRlbmlkYXMgYSBzdSB0YW1hw7FvIG9yaWdpbmFsIHBhcmEgcG9kZXIgY29tcGFyYXJsYXMgY29uIGVzdGFzLiBWZW1vcyBxdWUgZW4gZXN0ZSBjYXNvIGxhIGVsaW1pbmFjacOzbiBkZSBydWlkbyBoYSBzaWRvIG11eSBidWVuYSB5IG5vIGhheSBhcGVuYXMgZGlzdG9yc2nDs24gbmkgc3Vhdml6YWRvIGRlIGxvcyBib3JkZXMuDQpgYGB7cn0NCmltYWdlc19zaW5fcnVpZG8gPC0gTWFwKHJlc2l6ZV9pbXdkX3RvX29yaWdpbmFsLCBpbWFnZXNfc2luX3J1aWRvcywgYygiMiIsICIzIiwgIjMiKSApDQpgYGANCg0KYGBge3J9DQpwYXIobWZyb3cgPSBjKDMsIDIpLCBjZXggPSAwLjUpDQoNCmRpc3BsYXkoSW1hZ2UoaW1hZ2VzW1syXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQ0KdGl0bGUoJ0ltYWdlbiAyIG9yaWdpbmFsJykNCmRpc3BsYXkoSW1hZ2UoaW1hZ2VzX3Npbl9ydWlkb1tbMV1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykNCnRpdGxlKCdJbWFnZW4gMiBzaW4gcnVpZG8nKQ0KDQpkaXNwbGF5KEltYWdlKGltYWdlc1tbM11dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykNCnRpdGxlKCdJbWFnZW4gMyBvcmlnaW5hbCcpDQpkaXNwbGF5KEltYWdlKGltYWdlc19zaW5fcnVpZG9bWzJdXSwgY29sb3Jtb2RlID0gJ0NvbG9yJyksIG1ldGhvZD0ncicpDQp0aXRsZSgnSW1hZ2VuIDMgc2luIHJ1aWRvJykNCg0KZGlzcGxheShJbWFnZShpbWFnZXNbWzNdXSwgY29sb3Jtb2RlID0gJ0NvbG9yJyksIG1ldGhvZD0ncicpDQp0aXRsZSgnSW1hZ2VuIDMgb3JpZ2luYWwnKQ0KZGlzcGxheShJbWFnZShpbWFnZXNfc2luX3J1aWRvW1szXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQ0KdGl0bGUoJ0ltYWdlbiAzIHNpbiBydWlkbycpDQpgYGANCmBgYHtyfQ0Kcm0oaW1hZ2VzX3JlY29ydGFkYXMsIGltYWdlc19zaW5fcnVpZG8sIGltYWdlc19zaW5fcnVpZG9zKQ0KYGBgDQoNCiMjIyBBbXBsaWFuZG8gbGFzIGltw6FnZW5lcw0KDQpWYW1vcyBoYSBlbXBsZWFyIGFob3JhIGVsIG90cm8gbcOpdG9kbzogYXVtZW50YW5kbyBsYSBtYXRyaXogY29uIDAgw7MgMSBoYXN0YSBlbCB0YW1hw7FvIGFkZWN1YWRvIHBhcmEgcG9kZXIgYXBsaWNhciBsYSBmdW5jacOzbiBgaW13ZGAuIENvbWVuemFtb3MgZ2VuZXJhbmRvIGxhcyBpbcOhZ2VuZXMgY29uIHJ1aWRvLiBTZSBlbGlnZSBsYSBmb3RvIGNvbiBtZW5vciByZXNvbHVjacOzbiAoaW1hZ2VuIDUpIHBvcnF1ZSwgYWwgYXBsaWNhciBsYSBmdW5jacOzbiBhIGZvdG9zIGNvbiBtYXlvciBjYW50aWRhZCBkZSBww614ZWxlcywgc2UgZ2VuZXJhIHVuIHByb2JsZW1hIGNvbiBlbCB1c28gZGUgbGEgbWVtb3JpYSBlbiBSIHBhcmEgY2FyZ2FybGFzIGRlYmlkbyBhIHF1ZSBsYSBtYXRyaXogc2UgaGFjZSBkZW1hc2lhZG8gZ3JhbmRlLiBTZSBhcGxpY2FuIGRpc3RpbnRvcyBydWlkb3MgYSBsYSAgbWlzbWEgaW1hZ2VuLCBzZSBwcmVzZW50YSB1bmEgZm90byBkZSBlamVtcGxvIGNvbiBydWlkbyBnYXVzc2lhbm8uDQpgYGB7cixlY2hvPUZBTFNFfQ0KZ2F1c3NpYW5fbm9pc2VfNTwtYWRkX25vaXNlX3RvX2ltYWdlKCI1IiwgImdhdXNzaWFuIiwgbGlzdChzdGRfZGV2ID0gMC41KSkNCnNpbnVfaGlnaF9ub2lzZV81PC1hZGRfbm9pc2VfdG9faW1hZ2UoIjUiLCAic2ludXNvaWRhbF9oaWdoIiwgbGlzdChmcmVxdWVuY3kgPSAyNSwgYW1wbGl0dWRlID0gMC4yKSkNCnNpbnVfbG93X25vaXNlXzU8LWFkZF9ub2lzZV90b19pbWFnZSgiNSIsICJzaW51c29pZGFsX2xvdyIsIGxpc3QoZnJlcXVlbmN5ID0gMiwgYW1wbGl0dWRlID0gMC4yKSkNCnNhbHRfcGVwcGVyX25vaXNlXzU8LWFkZF9ub2lzZV90b19pbWFnZSgiNSIsICJzYWx0X3BlcHBlciIsIGxpc3QoZXBzaWxvbiA9IDAuMSkpDQpnYW1tYV9ub2lzZV81PC1hZGRfbm9pc2VfdG9faW1hZ2UoIjUiLCAiZ2FtbWEiLCBsaXN0KGxvb2tzID0gMikpDQp1bmlmX25vaXNlXzU8LWFkZF9ub2lzZV90b19pbWFnZSgiNSIsICJ1bmlmb3JtX211bHRpcGxpY2F0aXZlIiwgbGlzdChsb29rcyA9IDIpKQ0KYGBgDQoNCg0KYGBge3IsZWNobz1GQUxTRX0NCg0KaW1hZ2VuX25vaXNlIDwtIGxpc3QoZ2F1c3NpYW5fbm9pc2VfNSxzaW51X2hpZ2hfbm9pc2VfNSxzaW51X2xvd19ub2lzZV81LHNhbHRfcGVwcGVyX25vaXNlXzUsIGdhbW1hX25vaXNlXzUsIHVuaWZfbm9pc2VfNSkNCm5hbWVzKGltYWdlbl9ub2lzZSkgPC0gYygnTm9pc2U6IGdhdXNzaWFuJywgJ05vaXNlOiBzaW51c29pZGFsX2hpZ2gnLCdOb2lzZTogc2ludXNvaWRhbF9sb3cnLCdOb2lzZTogc2FsdF9wZXBwZXInLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAnTm9pc2U6IGdhbW1hJywgJ05vaXNlOiB1bmlmJykNCmBgYA0KDQpTZSBnZW5lcmEgdW5hIGZ1bmNpw7NuIHF1ZSBhanVzdGEgY3VhbHF1aWVyIGltYWdlbiByZWN0YW5ndWxhciBhIHVuIHRhbWHDsW8gY3VhZHJhZG8sIG1hbnRlbmllbmRvIHN1cyBwcm9wb3JjaW9uZXMgb3JpZ2luYWxlcyBhbCBhZ3JlZ2FyIHJlbGxlbm8gc2kgZXMgbmVjZXNhcmlvLiBFc3RhIHRyYW5zZm9ybWFjacOzbiBhc2VndXJhIHF1ZSBsYSBpbWFnZW4gc2VhIGNvbXBhdGlibGUgY29uIGVsIGFsZ29yaXRtbyBJTVdELg0KDQpgYGB7cn0NCmhhY2VyX2N1YWRyYWRhX3BvdGVuY2lhXzIgPC0gZnVuY3Rpb24oaW1hZ2VuKSB7DQogIG5fZmlsYXMgPC0gZGltKGltYWdlbilbMV0NCiAgbl9jb2x1bW5hcyA8LSBkaW0oaW1hZ2VuKVsyXQ0KICANCiAgbnVldm9fdGFtYW5vIDwtIG1heChuX2ZpbGFzLCBuX2NvbHVtbmFzKQ0KICANCiAgc2lndWllbnRlX3BvdGVuY2lhXzIgPC0gMl5jZWlsaW5nKGxvZzIobnVldm9fdGFtYW5vKSkNCiAgDQogIGltYWdlbl9jdWFkcmFkYSA8LSBhcnJheSgwLCBkaW0gPSBjKHNpZ3VpZW50ZV9wb3RlbmNpYV8yLCBzaWd1aWVudGVfcG90ZW5jaWFfMiwgZGltKGltYWdlbilbM10pKSANCiAgDQogIGltYWdlbl9jdWFkcmFkYVsxOm5fZmlsYXMsIDE6bl9jb2x1bW5hcywgXSA8LSBpbWFnZW4NCiAgDQogIHJldHVybihpbWFnZW5fY3VhZHJhZGEpDQp9DQoNCmBgYA0KDQpBcGxpY2Ftb3MgZXN0YSBmdW5jacOzbiBhIGxhIGltYWdlbiA1IGNvbiBsb3MgZGlzdGludG9zIHRpcG9zIGRlIHJ1aWRvLg0KDQpgYGB7cixlY2hvPUZBTFNFfQ0KZm90b3NfY3VhZHJhZGFzIDwtIGxhcHBseShpbWFnZW5fbm9pc2UsIGhhY2VyX2N1YWRyYWRhX3BvdGVuY2lhXzIpDQpgYGANCg0KDQpgYGB7cixlY2hvPUZBTFNFfQ0KcGFyKG1mcm93ID0gYygxLCAyKSkgICMgMSBmaWxhLCAyIGNvbHVtbmFzDQoNCiMgTW9zdHJhciBsYSBmb3RvIG9yaWdpbmFsDQpmb3RvX29yaWdpbmFsPC1oYWNlcl9jdWFkcmFkYV9wb3RlbmNpYV8yKGltYWdlc1tbNV1dKQ0KDQpFQkltYWdlOjpkaXNwbGF5KEltYWdlKGZvdG9fb3JpZ2luYWwsIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2QgPSAncicpDQptdGV4dCgiSW1hZ2VuIGF1bWVudGFkYSBlbiBwb3RlbmNpYSAyIiwgc2lkZSA9IDMsIGxpbmUgPSAxLjUsIGNleCA9IDEpDQoNCmk9MQ0KDQpFQkltYWdlOjpkaXNwbGF5KEltYWdlKGZvdG9zX2N1YWRyYWRhc1tbaV1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kID0gJ3InKQ0KbXRleHQobmFtZXMoZm90b3NfY3VhZHJhZGFzKVtpXSwgc2lkZSA9IDMsIGxpbmUgPSAxLjUsIGNleCA9IDEpDQoNCiNwYXIobWZyb3cgPSBjKDEsIDEpKQ0KDQpgYGANCg0KYGBge3IsZWNobz1GQUxTRX0NCg0KI2ZvcihpIGluIGMoMTo1KSl7DQojcHJ1ZWJhPC1wcm9jZXNhcl9pbWFnZW5fd2F2ZWxldChmb3Rvc19jdWFkcmFkYXNbW2ldXSwgdGlwbyA9ICJoYXJkIiwgcG9saWN5ID0gIyJ1bml2ZXJzYWwiKQ0KI0VCSW1hZ2U6OmRpc3BsYXkocHJ1ZWJhWzE6MTYwMCwxOjEwMDAsXSwgbWV0aG9kID0gJ3InLCB0aXRsZSA9ICdJbWFnZW4gV2F2ZWxldCBzaW4gI3J1aWRvJykNCiNtdGV4dChuYW1lcyhmb3Rvc19jdWFkcmFkYXMpW2ldLCBzaWRlID0gMywgbGluZSA9IDMuMiwgY2V4ID0gMSkNCg0KI30NCmBgYA0KDQoNCmBgYHtyLGVjaG89RkFMU0V9DQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoRUJJbWFnZSkpDQoNCmxpYnJhcnkobWFnaWNrKQ0KaW1wcmltaXI8LWZ1bmN0aW9uKGkpew0KIyBDcmVhciBsYSBpbWFnZW4gb3JpZ2luYWwgKGNvbiBydWlkbykNCnJ1aWRvMSA8LSBJbWFnZShmb3Rvc19jdWFkcmFkYXNbW2ldXSwgY29sb3Jtb2RlID0gJ0NvbG9yJykNCmltZ19yYXN0ZXIgPC0gYXMucmFzdGVyKHJ1aWRvMSkNCmltZ19tYWdpY2sxIDwtIGltYWdlX3JlYWQoaW1nX3Jhc3RlcikNCg0KIyBSZWNvcnRhciBsYSBpbWFnZW4gYSAxMzAweDEyMDAgZGVzZGUgbGEgZXNxdWluYSBzdXBlcmlvciBpenF1aWVyZGENCmltZ19tYWdpY2sxX3JlY29ydGFkYSA8LSBpbWFnZV9jcm9wKGltZ19tYWdpY2sxLCAiMTYwMHgxMDYwKzEwKzEwIikNCg0KIyBEaWJ1amFyIGVsIHTDrXR1bG8gc29icmUgbGEgaW1hZ2VuIHJlY29ydGFkYQ0KaW1nX21hZ2ljazFfcmVjb3J0YWRhIDwtIGltYWdlX2RyYXcoaW1nX21hZ2ljazFfcmVjb3J0YWRhKQ0KdGV4dCh4ID0gMjIwLCB5ID0gMjAsIGxhYmVscyA9IHBhc3RlMChuYW1lcyhmb3Rvc19jdWFkcmFkYXMpW2ldKSwgY29sID0gIndoaXRlIiwgY2V4ID0gNCkgICMgQWp1c3RhIGxhIHBvc2ljacOzbiB5IGVsIHRhbWHDsW8NCmRldi5vZmYoKSAgIyBUZXJtaW5hIGRlIGRpYnVqYXIgZW4gbGEgaW1hZ2VuDQoNCiMgUHVlZGVzIGNvbnRpbnVhciBjb24gZWwgbWlzbW8gcHJvY2VzbyBwYXJhIGxhcyBvdHJhcyBpbcOhZ2VuZXMNCiMgUmVjb3J0YXIgbGFzIGltw6FnZW5lcyBwcm9jZXNhZGFzIHkgYcOxYWRpcmxlcyB0w610dWxvcw0KDQpydWlkbzFfdW5pdmVyc2FsIDwtIHByb2Nlc2FyX2ltYWdlbl93YXZlbGV0KGZvdG9zX2N1YWRyYWRhc1tbaV1dLCB0aXBvID0gImhhcmQiLCBwb2xpY3kgPSAidW5pdmVyc2FsIikNCmltZ19yYXN0ZXIgPC0gYXMucmFzdGVyKHJ1aWRvMV91bml2ZXJzYWwpDQppbWdfbWFnaWNrMiA8LSBpbWFnZV9yZWFkKGltZ19yYXN0ZXIpDQoNCiMgUmVjb3J0YXIgbGEgaW1hZ2VuIHByb2Nlc2FkYQ0KaW1nX21hZ2ljazJfcmVjb3J0YWRhIDwtIGltYWdlX2Nyb3AoaW1nX21hZ2ljazIsICIxNjAweDEwNjArMTArMTAiKQ0KDQojIERpYnVqYXIgZWwgdMOtdHVsbyBzb2JyZSBsYSBpbWFnZW4gcmVjb3J0YWRhDQppbWdfbWFnaWNrMl9yZWNvcnRhZGEgPC0gaW1hZ2VfZHJhdyhpbWdfbWFnaWNrMl9yZWNvcnRhZGEpDQp0ZXh0KHggPSAyMjAsIHkgPSAyMCwgbGFiZWxzID0gIldhdmVsZXQgLSBVbml2ZXJzYWwiLCBjb2wgPSAid2hpdGUiLCBjZXggPSA0KSANCmRldi5vZmYoKQ0KDQpydWlkbzFfZmRyIDwtIHByb2Nlc2FyX2ltYWdlbl93YXZlbGV0KGZvdG9zX2N1YWRyYWRhc1tbaV1dLCB0aXBvID0gImhhcmQiLCBwb2xpY3kgPSAiZmRyIikNCmltZ19yYXN0ZXIgPC0gYXMucmFzdGVyKHJ1aWRvMV9mZHIpDQppbWdfbWFnaWNrMyA8LSBpbWFnZV9yZWFkKGltZ19yYXN0ZXIpDQoNCiMgUmVjb3J0YXIgbGEgaW1hZ2VuIHByb2Nlc2FkYQ0KaW1nX21hZ2ljazNfcmVjb3J0YWRhIDwtIGltYWdlX2Nyb3AoaW1nX21hZ2ljazMsICIxNjAweDEwNjArMTArMTAiKQ0KDQojIERpYnVqYXIgZWwgdMOtdHVsbyBzb2JyZSBsYSBpbWFnZW4gcmVjb3J0YWRhDQppbWdfbWFnaWNrM19yZWNvcnRhZGEgPC0gaW1hZ2VfZHJhdyhpbWdfbWFnaWNrM19yZWNvcnRhZGEpDQp0ZXh0KHggPSAyMjAsIHkgPSAyMCwgbGFiZWxzID0gIldhdmVsZXQgLSBGRFIiLCBjb2wgPSAid2hpdGUiLCBjZXggPSA0KSANCmRldi5vZmYoKQ0KDQojIENvbWJpbmFyIGxhcyB0cmVzIGltw6FnZW5lcyByZWNvcnRhZGFzDQppbWFnZW5fY29tYmluYWRhX3JlY29ydGFkYSA8LSBpbWFnZV9hcHBlbmQoYyhpbWdfbWFnaWNrMV9yZWNvcnRhZGEsIGltZ19tYWdpY2syX3JlY29ydGFkYSwgaW1nX21hZ2ljazNfcmVjb3J0YWRhKSkNCg0KIyBNb3N0cmFyIGxhIGltYWdlbiBjb21iaW5hZGEgcmVjb3J0YWRhDQpyZXR1cm4oaW1hZ2VuX2NvbWJpbmFkYV9yZWNvcnRhZGEpDQp9DQpgYGANCg0KU2Ugb2JzZXJ2YSBxdWUgZWwgdW1icmFsIEZEUiBwb2Ryw61hIG9mcmVjZXIgdW5hIGxpZ2VyYSBtZWpvcmEgZW4gbGEgZWxpbWluYWNpw7NuIGRlIHJ1aWRvIGVuIGNvbXBhcmFjacOzbiBjb24gZWwgdW1icmFsIFVuaXZlcnNhbC4gRXN0byBzZSBkZWJlIGEgcXVlIGVsIEZEUiBlc3RpbWEgbGEgcHJvYmFiaWxpZGFkIGRlIHF1ZSB1biBjb2VmaWNpZW50ZSBkZSB3YXZlbGV0IHByb3ZlbmdhIGRlbCBydWlkbywgYmFzw6FuZG9zZSBlbiBsYSBkaXN0cmlidWNpw7NuIG5vcm1hbC4gQWwgYXN1bWlyIHF1ZSBlbCBydWlkbyBzaWd1ZSB1bmEgZGlzdHJpYnVjacOzbiBnYXVzc2lhbmEsIGVsIEZEUiBwdWVkZSBhanVzdGFyIGVsIHVtYnJhbCBkZSBtYW5lcmEgbcOhcyBkaW7DoW1pY2EsIGxvIHF1ZSBsZSBwZXJtaXRlIGlkZW50aWZpY2FyIHkgZWxpbWluYXIgbG9zIGNvZWZpY2llbnRlcyBydWlkb3NvcyBjb24gbWF5b3IgcHJlY2lzacOzbiwgYWwgdGllbXBvIHF1ZSBwcmVzZXJ2YSBsb3MgZGV0YWxsZXMgcmVsZXZhbnRlcyBkZSBsYSBpbWFnZW4uIEVuIGNhbWJpbywgZWwgdW1icmFsIFVuaXZlcnNhbCB1dGlsaXphIHVuIHVtYnJhbCBmaWpvIGJhc2FkbyBlbiBlbCB0YW1hw7FvIGRlIGxhIHNlw7FhbCwgbG8gcXVlIG5vIGNhcHR1cmEgY29tcGxldGFtZW50ZSBsYSB2YXJpYWJpbGlkYWQgZGVsIHJ1aWRvLiBDb21vIHJlc3VsdGFkbywgZWwgRkRSIHBvZHLDrWEgb2ZyZWNlciB1bmEgZWxpbWluYWNpw7NuIGRlIHJ1aWRvIG3DoXMgYWRhcHRhdGl2YSwgYXl1ZGFuZG8gYSBwcmVzZXJ2YXIgbWVqb3IgbG9zIGRldGFsbGVzIGRlIGxhIGltYWdlbiwgYXVucXVlIGxhIGRpZmVyZW5jaWEgZW4gbGEgcHLDoWN0aWNhIG5vIHNpZW1wcmUgc2VhIG1hcmNhZGFtZW50ZSBzaWduaWZpY2F0aXZhLg0KDQpFbGltaW5hciBSdWlkbyBHYXVzc2lhbm8NCg0KYGBge3IsZWNobz1GQUxTRX0NCnN1cHByZXNzTWVzc2FnZXMoaW1wcmltaXIoMSkpDQpgYGANCg0KICAgICAgICAgICAgICAgICAgICAgICAgIA0KRWxpbWluYXIgUnVpZG8gU2ludXNvaWRhbCBoaWdoIA0KDQpgYGB7cixlY2hvPUZBTFNFfQ0Kc3VwcHJlc3NNZXNzYWdlcyhpbXByaW1pcigyKSkNCmBgYA0KDQpFbGltaW5hciBSdWlkbyBTaW51c29pZGFsIGxvdyBodHRwOi8vbG9jYWxob3N0OjEwNDk3L3Nlc3Npb24vcHJldmlldy5wbmc/dmlld2VyX3BhbmU9MSZjYXBhYmlsaXRpZXM9MSZob3N0PWh0dHAlM0ElMkYlMkYxMjcuMC4wLjElM0EyOTY4Mw0KYGBge3IsZWNobz1GQUxTRX0NCnN1cHByZXNzTWVzc2FnZXMoaW1wcmltaXIoMykpDQpgYGANCg0KRWxpbWluYXIgUnVpZG8gU2FsdCBhbmQgcGVwcGVyIA0KYGBge3IsZWNobz1GQUxTRX0NCnN1cHByZXNzTWVzc2FnZXMoaW1wcmltaXIoNCkpDQpgYGANCg0KRWxpbWluYXIgUnVpZG8gdW5pZm9ybWUgDQpgYGB7cixlY2hvPUZBTFNFfQ0Kc3VwcHJlc3NNZXNzYWdlcyhpbXByaW1pcig1KSkNCmBgYA0KDQoNCkVsaW1pbmFyICBSdWlkbyBnYW1tYSANCmBgYHtyLGVjaG89RkFMU0V9DQpzdXBwcmVzc01lc3NhZ2VzKGltcHJpbWlyKDYpKQ0KYGBgDQoNCg0KDQpgYGB7cixlY2hvPUZBTFNFfQ0KbGlicmFyeShtYWdpY2spDQoNCmltcHJpbWlyX2FqdXN0ZXMgPC0gZnVuY3Rpb24oaSkgew0KICANCiAgIyBDcmVhciBsYSBpbWFnZW4gb3JpZ2luYWwgKGNvbiBydWlkbykNCiAgcnVpZG8xIDwtIEltYWdlKGZvdG9zX2N1YWRyYWRhc1tbaV1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKQ0KICBpbWdfcmFzdGVyIDwtIGFzLnJhc3RlcihydWlkbzEpDQogIGltZ19tYWdpY2sxIDwtIGltYWdlX3JlYWQoaW1nX3Jhc3RlcikNCiAgDQogICMgUmVjb3J0YXIgbGEgaW1hZ2VuIGEgMTYwMHgxMDYwIGRlc2RlIGxhIGVzcXVpbmEgc3VwZXJpb3IgaXpxdWllcmRhDQogIGltZ19tYWdpY2sxX3JlY29ydGFkYSA8LSBpbWFnZV9jcm9wKGltZ19tYWdpY2sxLCAiMTYwMHgxMDYwKzEwKzEwIikNCiAgDQogICMgRGlidWphciBlbCB0w610dWxvIHNvYnJlIGxhIGltYWdlbiByZWNvcnRhZGENCiAgaW1nX21hZ2ljazFfcmVjb3J0YWRhIDwtIGltYWdlX2RyYXcoaW1nX21hZ2ljazFfcmVjb3J0YWRhKQ0KICB0ZXh0KHggPSAyMDAsIHkgPSAyMCwgbGFiZWxzID0gcGFzdGUwKG5hbWVzKGZvdG9zX2N1YWRyYWRhcylbaV0pLCBjb2wgPSAid2hpdGUiLCBjZXggPSAzLjUpICANCiAgZGV2Lm9mZigpICANCiAgDQogICMgUHJvY2VzYXIgaW1hZ2VuIGNvbiBmaWx0cm8gV2F2ZWxldCBGRFINCiAgcnVpZG8xX2ZkciA8LSBwcm9jZXNhcl9pbWFnZW5fd2F2ZWxldChmb3Rvc19jdWFkcmFkYXNbW2ldXSwgdGlwbyA9ICJoYXJkIiwgcG9saWN5ID0gImZkciIpDQogIGltZ19yYXN0ZXIgPC0gYXMucmFzdGVyKHJ1aWRvMV9mZHIpDQogIGltZ19tYWdpY2szIDwtIGltYWdlX3JlYWQoaW1nX3Jhc3RlcikNCiAgaW1nX21hZ2ljazNfcmVjb3J0YWRhIDwtIGltYWdlX2Nyb3AoaW1nX21hZ2ljazMsICIxNjAweDEwNjArMTArMTAiKQ0KICBpbWdfbWFnaWNrX3JlY29ydGFkYSA8LSBpbWFnZV9jcm9wKGltZ19tYWdpY2szLCAiMTYwMHgxMDYwKzEwKzEwIikNCg0KICAjIERpYnVqYXIgdMOtdHVsbyAiV2F2ZWxldCAtIEZEUiIgc29icmUgbGEgaW1hZ2VuIHJlY29ydGFkYQ0KICBpbWdfbWFnaWNrM19yZWNvcnRhZGEgPC0gaW1hZ2VfZHJhdyhpbWdfbWFnaWNrM19yZWNvcnRhZGEpDQogIHRleHQoeCA9IDIwMCwgeSA9IDIwLCBsYWJlbHMgPSAiV2F2ZWxldCAtIEZEUiIsIGNvbCA9ICJ3aGl0ZSIsIGNleCA9IDMuNSkgIA0KICBkZXYub2ZmKCkgICMgVGVybWluYSBkZSBkaWJ1amFyIGVuIGxhIGltYWdlbg0KICANCiAgIyBBcGxpY2FyIGZpbHRybyBkZSBrZXJuZWwgcGFzbyBhbHRvDQogIGtlcm5lbF9wYXNvX2FsdG8gPC0gbWF0cml4KGMoMCwgLTEsIDAsIC0xLCA1LCAtMSwgMCwgLTEsIDApLCBucm93ID0gMywgbmNvbCA9IDMpDQogIGZpbHRyYWRhX2FsdG8gPC0gaW1hZ2VfY29udm9sdmUoaW1nX21hZ2lja19yZWNvcnRhZGEsIGtlcm5lbF9wYXNvX2FsdG8pDQogIA0KICAjIEFncmVnYXIgdMOtdHVsbyAiRmlsdHJvIEFsdG8iIHNvYnJlIGxhIGltYWdlbiBmaWx0cmFkYSBjb24gZWwgZmlsdHJvIGRlIHBhc28gYWx0bw0KICBmaWx0cmFkYV9hbHRvIDwtIGltYWdlX2RyYXcoZmlsdHJhZGFfYWx0bykNCiAgdGV4dCh4ID0gMjAwLCB5ID0gMjAsIGxhYmVscyA9ICJGaWx0cm8gQWx0byIsIGNvbCA9ICJ3aGl0ZSIsIGNleCA9IDMuNSkgICMgVMOtdHVsbyAiRmlsdHJvIEFsdG8iDQogIGRldi5vZmYoKSAgIyBUZXJtaW5hIGRlIGRpYnVqYXIgZW4gbGEgaW1hZ2VuDQogIA0KICAjIEFwbGljYXIgY29udHJhc3RlIGEgbGEgaW1hZ2VuDQogIGNvbmNvbnRyYXN0ZSA8LSBpbWFnZV9jb250cmFzdChpbWdfbWFnaWNrX3JlY29ydGFkYSkNCiAgY29uY29udHJhc3RlIDwtIGltYWdlX2RyYXcoY29uY29udHJhc3RlKQ0KICB0ZXh0KHggPSAyMDAsIHkgPSAyMCwgbGFiZWxzID0gIkNvbnRyYXN0ZSIsIGNvbCA9ICJ3aGl0ZSIsIGNleCA9IDMuNSkgICMgVMOtdHVsbyAiRmlsdHJvIEFsdG8iDQogIGRldi5vZmYoKSANCiAgDQogICMgQ29tYmluYXIgbGFzIHRyZXMgaW3DoWdlbmVzIChXYXZlbGV0LCBGaWx0cm8gQWx0bywgeSBDb250cmFzdGUpDQogIGltYWdlbl9jb21iaW5hZGFfcmVjb3J0YWRhIDwtIGltYWdlX2FwcGVuZChjKGltZ19tYWdpY2tfcmVjb3J0YWRhMywgZmlsdHJhZGFfYWx0bywgY29uY29udHJhc3RlKSkNCiAgDQogICMgTW9zdHJhciBsYSBpbWFnZW4gY29tYmluYWRhIHJlY29ydGFkYQ0KICByZXR1cm4oaW1hZ2VuX2NvbWJpbmFkYV9yZWNvcnRhZGEpDQp9DQoNCmBgYA0KDQoNCg0KYGBge3IsZWNobz1GQUxTRX0NCiNzdXBwcmVzc01lc3NhZ2VzKGltcHJpbWlyX2FqdXN0ZXMoMSkpDQpgYGANCg0KYGBge3IsZWNobz1GQUxTRX0NCiNzdXBwcmVzc01lc3NhZ2VzKGltcHJpbWlyX2FqdXN0ZXMoMikpDQpgYGANCg0KYGBge3IsZWNobz1GQUxTRX0NCiNzdXBwcmVzc01lc3NhZ2VzKGltcHJpbWlyX2FqdXN0ZXMoMykpDQpgYGANCg0KYGBge3IsZWNobz1GQUxTRX0NCiNzdXBwcmVzc01lc3NhZ2VzKGltcHJpbWlyX2FqdXN0ZXMoNCkpDQpgYGANCg0KYGBge3IsZWNobz1GQUxTRX0NCiNzdXBwcmVzc01lc3NhZ2VzKGltcHJpbWlyX2FqdXN0ZXMoNSkpDQpgYGANCg0KYGBge3IsZWNobz1GQUxTRX0NCiNzdXBwcmVzc01lc3NhZ2VzKGltcHJpbWlyX2FqdXN0ZXMoNikpDQpgYGANCg0KDQpgYGB7cn0NCnJtKGdhbW1hX25vaXNlXzUsZ2F1c3NpYW5fbm9pc2VfNSwgc2FsdF9wZXBwZXJfbm9pc2VfNSxzaW51X2hpZ2hfbm9pc2VfNSxzaW51X2xvd19ub2lzZV81LHVuaWZfbm9pc2VfNSxoYWNlcl9jdWFkcmFkYV9wb3RlbmNpYV8yLHByb2Nlc2FyX2ltYWdlbl93YXZlbGV0X3NpbnVzb2lkYWwsIHByb2Nlc2FyX2ltYWdlbl93YXZlbGV0LCByZXNpemVfaW13ZCwgcmVzaXplX2ltd2RfdG9fb3JpZ2luYWwsIGltcHJpbWlyLCBmb3Rvc19jdWFkcmFkYXMsIGltYWdlbl9ub2lzZSkNCmBgYA0KDQoNCg0KIyMgRnVuY2nDs24gZGVub2lzZS5kd3QuMmQNCg0KRW4gc2VndW5kbyBsdWdhciwgdmFtb3MgYSBlbXBsZWFyIGxhIGZ1bmNpw7NuIGBkZW5vaXNlLmR3dC4yZGAsIHVuIG3DqXRvZG8gbcOhcyBkaXJlY3RvIHF1ZSByZWFsaXphIGxhIHRyYW5zZm9ybWFkYSB3YXZlbGV0LCBlbCB0aHJlc2hvbGRpbmcgeSBsYSB0cmFuc2Zvcm1hZGEgaW52ZXJzYSBlbiBlc3RhIGZ1bmNpw7NuIHlhIGltcGxlbWVudGFkYSBlbiBSLiBBIGNvbnRpbnVhY2nDs24gc2UgbXVlc3RyYW4gbG9zIGRpc3RpbnRvcyBmaWx0cm9zIHdhdmVsZXQgcXVlIHNlIHB1ZWRlbiBlc2NvZ2VyIGNvbW8gcGFyw6FtZXRyb3MgZGUgZXN0YSBmdW5jacOzbi4NCg0KDQpgYGB7cn0NCg0KIyBkaXIuY3JlYXRlKCJkZW5vaXNlZHd0MmQiLCBzaG93V2FybmluZ3MgPSBGQUxTRSkNCg0KIyBwbmcoImRlbm9pc2Vkd3QyZC93YXZlbGV0X2ZpbHRlcnMucG5nIiwgd2lkdGggPSA4MDAsIGhlaWdodCA9IDQwMCkNCg0KZmlsdHJvIDwtIGMoJ2Q0JywgJ2xhOCcsICdibDE0JywgJ21iOCcpDQoNCnBhcihtZnJvdyA9IGMoMiwgNCkpDQpmb3IgKGYgaW4gZmlsdHJvKSB7DQogICMgT2J0ZW5lciBsb3MgY29lZmljaWVudGVzIGRlbCBmaWx0cm8gd2F2ZWxldA0KICB3YXZlbGV0X2ZpbHRlciA8LSB3YXZlLmZpbHRlcihmKQ0KICANCiAgIyBBanVzdGFyIGNvZWZpY2llbnRlcyBwYXJhIHF1ZSBlbXBpZWNlbiB5IHRlcm1pbmVuIGVuIDANCiAgaF9hZGp1c3RlZCA8LSBjKDAsIHdhdmVsZXRfZmlsdGVyJGhwZiwgMCkNCiAgZ19hZGp1c3RlZCA8LSBjKDAsIHdhdmVsZXRfZmlsdGVyJGxwZiwgMCkNCiAgDQogICMgQ2FsY3VsYXIgZWwgcmFuZ28gc2ltw6l0cmljbyBwYXJhIGVsIGVqZSBZDQogIG1heF92YWwgPC0gbWF4KGFicyhjKGhfYWRqdXN0ZWQsIGdfYWRqdXN0ZWQpKSkNCiAgeV9saW0gPC0gYygtbWF4X3ZhbCwgbWF4X3ZhbCkNCiAgDQogICMgR3JhZmljYXINCiAgcGxvdChoX2FkanVzdGVkLCB0eXBlID0gImwiLCBtYWluID0gcGFzdGUoIlBhc28gYWx0byIsIGYpLA0KICAgICAgIHhsYWIgPSAiw41uZGljZSIsIHlsYWIgPSAiQW1wbGl0dWQiLCBjb2wgPSAiYmx1ZSIsIGx3ZCA9IDIsDQogICAgICAgeWxpbSA9IHlfbGltKQ0KICBwbG90KGdfYWRqdXN0ZWQsIHR5cGUgPSAibCIsIG1haW4gPSBwYXN0ZSgiUGFzbyBiYWpvIiwgZiksDQogICAgICAgeGxhYiA9ICLDjW5kaWNlIiwgeWxhYiA9ICJBbXBsaXR1ZCIsIGNvbCA9ICJyZWQiLCBsd2QgPSAyLA0KICAgICAgIHlsaW0gPSB5X2xpbSkNCn0NCg0KIyBkZXYub2ZmKCkNCg0Kcm0oaF9hZGp1c3RlZCkNCnJtKGdfYWRqdXN0ZWQpDQpybShtYXhfdmFsKQ0Kcm0oeV9saW0pDQpgYGANCg0KRGFkbyBxdWUgc2UgaGFuIHJlYWxpemFkbyBtw7psdGlwbGVzIHBydWViYXMgeSBsb3MgcmVzdWx0YWRvcyBzb24gbXV5IHNpbWlsYXJlcywgc2UgbXVlc3RyYW4gc29sbyBhcXVlbGxvcyBxdWUgc2UgaGFuIGNvbnNpZGVyYWRvIG3DoXMgcmVsZXZhbnRlcy4NCg0KYGBge3J9DQpuaXZlbCA8LSBjKDIsIDMsIDQpDQp1bWJyYWwgPC0gYygic29mdCIsICJoYXJkIikNCg0KYGBgDQoNClByaW1lcm8sIG1vc3RyYW1vcyBlbCByZXN1bHRhZG8gZGVsIHByb2Nlc28gZGUgZGVub2lzaW5nIGRlIGxhIGltYWdlbiAxLCBhIGxhIHF1ZSBzZSBsZSBhw7FhZGnDsyBydWlkbyBnYXVzc2lhbm8uIEVzdGUgY2FzbyBzZSB1dGlsaXphIGNvbW8gZWplbXBsbyBwb3JxdWUgaWx1c3RyYSBsYXMgY29uY2x1c2lvbmVzIHF1ZSB0YW1iacOpbiBzb24gdsOhbGlkYXMgcGFyYSBlbCByZXN0byBkZSBpbcOhZ2VuZXMgeSB0aXBvcyBkZSBydWlkby4NCg0KYGBge3J9DQoNCiMgUGFyYSBldml0YXIgbGFyZ29zIHRpZW1wb3MgZGUgcHJvY2VzYWRvLCBzZSBnZW5lcmFyb24gbGFzIGltw6FnZW5lcyB5IHNlIGFsbWFjZW5hcm9uIGVuIGxhIGNhcnBldGEgZGVub2lzZWR3dDJkLCB5IHNlcsOhbiBsYXMgaW1hZ2VuZXMgZ3VhcmRhZGFzIGxhcyBxdWUgc2UgbXVlc3RyZW4gZW4gZWwgbWFya2Rvd24uDQoNCiMgcG5nKCJkZW5vaXNlZHd0MmQvZ2F1c3NpYW4ucG5nIiwgd2lkdGggPSA4MDAsIGhlaWdodCA9IDQwMCkNCiMgDQojIGlfc2lucnVpZG8gPC0gaW1hZ2VzW1sxXV0NCiMgaV9ydWlkb3NhIDwtICBhZGRfbm9pc2VfdG9faW1hZ2UoIjEiLCAiZ2F1c3NpYW4iLCBsaXN0KHN0ZF9kZXYgPSAwLjMpKQ0KIyAgICAgDQojIGlfZGltIDwtIGRpbShpX3NpbnJ1aWRvKQ0KIyBpX2xlbiA8LSBsZW5ndGgoaV9zaW5ydWlkbykNCiMgDQojIGlfZGVub2lzZWQgPC0gYXJyYXkoMCwgZGltID0gZGltKGlfcnVpZG9zYSkpDQojIA0KIyBwYXIobWZyb3c9IGMoMSwyKSkNCiMgZGlzcGxheShJbWFnZShpX3NpbnJ1aWRvLCBjb2xvcm1vZGUgPSAiQ29sb3IiKSwgbWV0aG9kID0gInIiKQ0KIyB0aXRsZSgiT3JpZ2luYWwiKQ0KIyBkaXNwbGF5KEltYWdlKGlfcnVpZG9zYSwgY29sb3Jtb2RlID0gIkNvbG9yIiksIG1ldGhvZCA9ICJyIikNCiMgdGl0bGUoIkltYWdlbiBjb24gcnVpZG8iKQ0KIyANCiMgZGV2Lm9mZigpDQojIHBuZygiZGVub2lzZWR3dDJkL2dhdXNzaWFuX2Rlbm9pc2VkLnBuZyIsIHdpZHRoID0gMTIwMCwgaGVpZ2h0ID0gMTgwMCkNCiMgDQojIHBhcihtZmNvbD0gYyg2LDQpKQ0KIyBmb3IgKGYgaW4gZmlsdHJvKXsNCiMgICBmb3IgKG4gaW4gbml2ZWwpew0KIyAgICAgZm9yICh1IGluIHVtYnJhbCl7DQojICAgICAgIGNhbmFsMSA8LSBkZW5vaXNlLmR3dC4yZChpX3J1aWRvc2FbLCwxXSwgd2YgPSBmLCBydWxlID0gdSwgSiA9IG4pDQojICAgICAgIGNhbmFsMiA8LSBkZW5vaXNlLmR3dC4yZChpX3J1aWRvc2FbLCwyXSwgd2YgPSBmLCBydWxlID0gdSwgSiA9IG4pDQojICAgICAgIGNhbmFsMyA8LSBkZW5vaXNlLmR3dC4yZChpX3J1aWRvc2FbLCwzXSwgd2YgPSBmLCBydWxlID0gdSwgSiA9IG4pDQojICAgICAgIGlfZGVub2lzZWQgPC0gYXJyYXkoYyhjYW5hbDEsIGNhbmFsMiwgY2FuYWwzKSwgZGltID0gYyhucm93KGNhbmFsMSksIG5jb2woY2FuYWwxKSwgMykpDQojICAgICAgICAgDQojICAgICAgIGRpc3BsYXkoSW1hZ2UoaV9kZW5vaXNlZCwgY29sb3Jtb2RlID0gIkNvbG9yIiksIG1ldGhvZCA9ICJyIikNCiMgICAgICAgdGl0bGUocGFzdGUwKCJXYXZlbGV0ICIsIGYsICIsIHVtYnJhbCAiICwgdSwgIiwgbml2ZWwgIiwgbikpDQojICAgICB9fX0NCiMgDQojIGRldi5vZmYoKQ0KDQppbmNsdWRlX2dyYXBoaWNzKCJkZW5vaXNlZHd0MmQvZ2F1c3NpYW4ucG5nIikNCmluY2x1ZGVfZ3JhcGhpY3MoImRlbm9pc2Vkd3QyZC9nYXVzc2lhbl9kZW5vaXNlZC5wbmciKQ0KDQpgYGANClNlIG9ic2VydmEgcXVlIGVsIHVzbyBkZSBsb3MgZGlmZXJlbnRlcyBmaWx0cm9zIHdhdmVsZXQgeSByZWdsYXMgZGUgYXBsaWNhY2nDs24gZGVsIHVtYnJhbCBubyBnZW5lcmEgcmVzdWx0YWRvcyBjb24gZGlmZXJlbmNpYXMgc2lnbmlmaWNhdGl2YXMuIFNpbiBlbWJhcmdvLCBlbCBuaXZlbCBkZSBkZXNjb21wb3NpY2nDs24gc8OtIHRpZW5lIHVuIGltcGFjdG8gbm90YWJsZS4NCg0KQ29uIHVuIG1lbm9yIG7Dum1lcm8gZGUgbml2ZWxlcyAoMi0zKSwgc2UgbG9ncmFuIGNvbnNlcnZhciBsb3MgZGV0YWxsZXMgZGUgbGEgaW1hZ2VuLCBwZXJvIGVsIHJ1aWRvIG5vIHNlIGVsaW1pbmEgY29tcGxldGFtZW50ZSwgY29uIHNvbG8gMiBuaXZlbGVzLCBlbCBydWlkbyBzaWd1ZSBzaWVuZG8gZXZpZGVudGUuIEFsIGF1bWVudGFyIGEgNCBuaXZlbGVzLCBzZSBvYnRpZW5lIHVuYSBpbWFnZW4gZW4gbGEgcXVlIGVsIHJ1aWRvIGVzIHByw6FjdGljYW1lbnRlIGluYXByZWNpYWJsZSwgYXVucXVlIGFwYXJlY2UgYWxnbyBtw6FzIHN1YXZpemFkYS4gU2kgc2UgYXVtZW50YXJhbiBhw7puIG3DoXMgbG9zIG5pdmVsZXMgZGUgZGVzY29tcG9zaWNpw7NuLCBzZSBlbXBlemFyw61hbiBhIHBlcmRlciBkZXRhbGxlcyBpbXBvcnRhbnRlcyBkZSBsYSBpbWFnZW4uIENvbiA0IG5pdmVsZXMgc2UgY29uc2lndWUgdW4gYnVlbiBlcXVpbGlicmlvIGVudHJlIGxhIGVsaW1pbmFjacOzbiBkZWwgcnVpZG8geSBsYSBjb25zZXJ2YWNpw7NuIGRlIGxvcyBkZXRhbGxlcy4gRXN0ZSBjb21wb3J0YW1pZW50byBzZSByZXBpdGUgZW4gbG9zIGRpc3RpbnRvcyB0aXBvcyBkZSBydWlkbyBhbmFsaXphZG9zLg0KDQpBIGNvbnRpbnVhY2nDs24gbW9zdHJhbW9zIHNvbG8gYWxndW5hcyBwYXJ0aWN1bGFyaWRhZGVzIHJlbGV2YW50ZXMgZGUgbG9zIHJlc3VsdGFkb3M6IA0KDQpQb3IgZWplbXBsbywgZW4gZWwgY2FzbyBkZWwgcnVpZG8gc2ludXNvaWRhbCBkZSBhbHRhIGZyZWN1ZW5jaWEsIGVsIHJlc3VsdGFkbyBkZWwgcHJvY2VzbyBkZSBlbGltaW5hY2nDs24gZGUgcnVpZG8gZXMgZGlzdGludG8uDQoNCmBgYHtyfQ0KDQojIHBuZygiZGVub2lzZWR3dDJkL3NpbmhpZ2gucG5nIiwgd2lkdGggPSA4MDAsIGhlaWdodCA9IDQwMCkNCiMgDQojIGlfc2lucnVpZG8gPC0gaW1hZ2VzW1syXV0NCiMgaV9ydWlkb3NhIDwtIGFkZF9ub2lzZV90b19pbWFnZSgiMiIsICJzaW51c29pZGFsX2hpZ2giLCBsaXN0KGZyZXF1ZW5jeSA9IDI1LCBhbXBsaXR1ZGUgPSAwLjIpKQ0KIyAgICAgDQojIGlfZGltIDwtIGRpbShpX3NpbnJ1aWRvKQ0KIyBpX2xlbiA8LSBsZW5ndGgoaV9zaW5ydWlkbykNCiMgDQojIGlfZGVub2lzZWQgPC0gYXJyYXkoMCwgZGltID0gZGltKGlfcnVpZG9zYSkpDQojIA0KIyBwYXIobWZyb3c9IGMoMSwyKSkNCiMgZGlzcGxheShJbWFnZShpX3NpbnJ1aWRvLCBjb2xvcm1vZGUgPSAiQ29sb3IiKSwgbWV0aG9kID0gInIiKQ0KIyB0aXRsZSgiT3JpZ2luYWwiKQ0KIyBkaXNwbGF5KEltYWdlKGlfcnVpZG9zYSwgY29sb3Jtb2RlID0gIkNvbG9yIiksIG1ldGhvZCA9ICJyIikNCiMgdGl0bGUoIkltYWdlbiBjb24gcnVpZG8iKQ0KIyANCiMgZGV2Lm9mZigpDQojIHBuZygiZGVub2lzZWR3dDJkL3NpbmhpZ2hfZGVub2lzZWQucG5nIiwgd2lkdGggPSAxMjAwLCBoZWlnaHQgPSAxODAwKQ0KIyANCiMgcGFyKG1mY29sPSBjKDYsNCkpDQojIGZvciAoZiBpbiBmaWx0cm8pew0KIyAgIGZvciAobiBpbiBuaXZlbCl7DQojICAgICBmb3IgKHUgaW4gdW1icmFsKXsNCiMgICAgICAgY2FuYWwxIDwtIGRlbm9pc2UuZHd0LjJkKGlfcnVpZG9zYVssLDFdLCB3ZiA9IGYsIHJ1bGUgPSB1LCBKID0gbikNCiMgICAgICAgY2FuYWwyIDwtIGRlbm9pc2UuZHd0LjJkKGlfcnVpZG9zYVssLDJdLCB3ZiA9IGYsIHJ1bGUgPSB1LCBKID0gbikNCiMgICAgICAgY2FuYWwzIDwtIGRlbm9pc2UuZHd0LjJkKGlfcnVpZG9zYVssLDNdLCB3ZiA9IGYsIHJ1bGUgPSB1LCBKID0gbikNCiMgICAgICAgaV9kZW5vaXNlZCA8LSBhcnJheShjKGNhbmFsMSwgY2FuYWwyLCBjYW5hbDMpLCBkaW0gPSBjKG5yb3coY2FuYWwxKSwgbmNvbChjYW5hbDEpLCAzKSkNCiMgICAgICAgICANCiMgICAgICAgZGlzcGxheShJbWFnZShpX2Rlbm9pc2VkLCBjb2xvcm1vZGUgPSAiQ29sb3IiKSwgbWV0aG9kID0gInIiKQ0KIyAgICAgICB0aXRsZShwYXN0ZTAoIldhdmVsZXQgIiwgZiwgIiwgdW1icmFsICIgLCB1LCAiLCBuaXZlbCAiLCBuKSkNCiMgICB9fX0NCiMgDQojIGRldi5vZmYoKQ0KDQppbmNsdWRlX2dyYXBoaWNzKCJkZW5vaXNlZHd0MmQvc2luaGlnaC5wbmciKQ0KaW5jbHVkZV9ncmFwaGljcygiZGVub2lzZWR3dDJkL3NpbmhpZ2hfZGVub2lzZWQucG5nIikNCg0KYGBgDQpFbiBlc3RlIGNhc28sIHZlbW9zIHF1ZSBlbiBuaW5ndW5vIGRlIGxvcyBjYXNvcyBsb2dyYW1vcyBlbGltaW5hciBjb21wbGV0YW1lbnRlIGVsIHJ1aWRvIHNpbnVzb2lkYWwuIEVzdG8gcHVlZGUgZGViZXJzZSBhIHF1ZSBlc3RlIHRpcG8gZGUgcnVpZG8gcHJlc2VudGEgY29tcG9uZW50ZXMgbXV5IGVzcGVjw61maWNhcyBkZSBhbHRhIGZyZWN1ZW5jaWEsIHF1ZSBwdWVkZW4gY29pbmNpZGlyIGNvbiBsYXMgZnJlY3VlbmNpYXMgZGUgbG9zIGRldGFsbGVzIGltcG9ydGFudGVzIGRlIGxhIGltYWdlbiwgbG8gcXVlIGRpZmljdWx0YSBlbGltaW5hciBlbCBydWlkbyBzaW4gY29tcHJvbWV0ZXIgbG9zIGRldGFsbGVzIGRlIGxhIGltYWdlbi4gRWwgcnVpZG8gc2ludXNvaWRhbCBlcmEgdGFtYmnDqW4gZWwgbcOhcyBjb21wbGljYWRvIGRlIGVsaW1pbmFyIGN1w6FuZG8gYXBsaWNhYmFtb3MgbGEgZnVuY2nDs24gYGltd2RgIHkgYHRocmVzaG9sZGAuDQoNCg0KYGBge3IsIGVjaG8gPSBGQUxTRX0NCiMgcG5nKCJkZW5vaXNlZHd0MmQvc2lubG93LnBuZyIsIHdpZHRoID0gODAwLCBoZWlnaHQgPSA0MDApDQojIA0KIyBpX3NpbnJ1aWRvIDwtIGltYWdlc1tbM11dDQojIGlfcnVpZG9zYSA8LSBhZGRfbm9pc2VfdG9faW1hZ2UoIjMiLCAic2ludXNvaWRhbF9sb3ciLCBsaXN0KGZyZXF1ZW5jeSA9IDUsIGFtcGxpdHVkZSA9IDAuMikpDQojIA0KIyBpX2RpbSA8LSBkaW0oaV9zaW5ydWlkbykNCiMgaV9sZW4gPC0gbGVuZ3RoKGlfc2lucnVpZG8pDQojIA0KIyBpX2Rlbm9pc2VkIDwtIGFycmF5KDAsIGRpbSA9IGRpbShpX3J1aWRvc2EpKQ0KIyANCiMgcGFyKG1mcm93PSBjKDEsMikpDQojIGRpc3BsYXkoSW1hZ2UoaV9zaW5ydWlkbywgY29sb3Jtb2RlID0gIkNvbG9yIiksIG1ldGhvZCA9ICJyIikNCiMgdGl0bGUoIk9yaWdpbmFsIikNCiMgZGlzcGxheShJbWFnZShpX3J1aWRvc2EsIGNvbG9ybW9kZSA9ICJDb2xvciIpLCBtZXRob2QgPSAiciIpDQojIHRpdGxlKCJJbWFnZW4gY29uIHJ1aWRvIikNCiMgDQojIGRldi5vZmYoKQ0KIyBwbmcoImRlbm9pc2Vkd3QyZC9zaW5sb3dfZGVub2lzZWQucG5nIiwgd2lkdGggPSAxMjAwLCBoZWlnaHQgPSAxODAwKQ0KIyANCiMgcGFyKG1mY29sPSBjKDYsNCkpDQojIGZvciAoZiBpbiBmaWx0cm8pew0KIyAgIGZvciAobiBpbiBuaXZlbCl7DQojICAgICBmb3IgKHUgaW4gdW1icmFsKXsNCiMgICAgICAgY2FuYWwxIDwtIGRlbm9pc2UuZHd0LjJkKGlfcnVpZG9zYVssLDFdLCB3ZiA9IGYsIHJ1bGUgPSB1LCBKID0gbikNCiMgICAgICAgY2FuYWwyIDwtIGRlbm9pc2UuZHd0LjJkKGlfcnVpZG9zYVssLDJdLCB3ZiA9IGYsIHJ1bGUgPSB1LCBKID0gbikNCiMgICAgICAgY2FuYWwzIDwtIGRlbm9pc2UuZHd0LjJkKGlfcnVpZG9zYVssLDNdLCB3ZiA9IGYsIHJ1bGUgPSB1LCBKID0gbikNCiMgICAgICAgaV9kZW5vaXNlZCA8LSBhcnJheShjKGNhbmFsMSwgY2FuYWwyLCBjYW5hbDMpLCBkaW0gPSBjKG5yb3coY2FuYWwxKSwgbmNvbChjYW5hbDEpLCAzKSkNCiMgDQojICAgICAgIGRpc3BsYXkoSW1hZ2UoaV9kZW5vaXNlZCwgY29sb3Jtb2RlID0gIkNvbG9yIiksIG1ldGhvZCA9ICJyIikNCiMgICAgICAgdGl0bGUocGFzdGUwKCJXYXZlbGV0ICIsIGYsICIsIHVtYnJhbCAiICwgdSwgIiwgbml2ZWwgIiwgbikpDQojICAgfX19DQojIA0KIyBkZXYub2ZmKCkNCg0KIyBpbmNsdWRlX2dyYXBoaWNzKCJkZW5vaXNlZHd0MmQvc2lubG93LnBuZyIpDQojIGluY2x1ZGVfZ3JhcGhpY3MoImRlbm9pc2Vkd3QyZC9zaW5sb3dfZGVub2lzZWQucG5nIikNCg0KYGBgDQoNCk90cm8gcnVpZG8gcXVlIHRpZW5lIHJlc3VsdGFkb3MgcGVjdWxpYXJlcyBlcyBlbCBydWlkbyBkZSB0aXBvIHNhbHQtYW5kLXBlcHBlci4NCg0KYGBge3J9DQoNCiMgcG5nKCJkZW5vaXNlZHd0MmQvcGVwcGVyLnBuZyIsIHdpZHRoID0gODAwLCBoZWlnaHQgPSA0MDApDQojIA0KIyBpX3NpbnJ1aWRvIDwtIGltYWdlc1tbNF1dDQojIGlfcnVpZG9zYSA8LSBhZGRfbm9pc2VfdG9faW1hZ2UoIjQiLCAic2FsdF9wZXBwZXIiLCBsaXN0KGVwc2lsb24gPSAwLjEpKQ0KIyAgICAgDQojIGlfZGltIDwtIGRpbShpX3NpbnJ1aWRvKQ0KIyBpX2xlbiA8LSBsZW5ndGgoaV9zaW5ydWlkbykNCiMgDQojIGlfZGVub2lzZWQgPC0gYXJyYXkoMCwgZGltID0gZGltKGlfcnVpZG9zYSkpDQojIA0KIyBwYXIobWZyb3c9IGMoMSwyKSkNCiMgZGlzcGxheShJbWFnZShpX3NpbnJ1aWRvLCBjb2xvcm1vZGUgPSAiQ29sb3IiKSwgbWV0aG9kID0gInIiKQ0KIyB0aXRsZSgiT3JpZ2luYWwiKQ0KIyBkaXNwbGF5KEltYWdlKGlfcnVpZG9zYSwgY29sb3Jtb2RlID0gIkNvbG9yIiksIG1ldGhvZCA9ICJyIikNCiMgdGl0bGUoIkltYWdlbiBjb24gcnVpZG8iKQ0KIyANCiMgZGV2Lm9mZigpDQojIHBuZygiZGVub2lzZWR3dDJkL3BlcHBlcl9kZW5vaXNlZC5wbmciLCB3aWR0aCA9IDEyMDAsIGhlaWdodCA9IDE4MDApDQojIA0KIyBwYXIobWZjb2w9IGMoNiw0KSkNCiMgZm9yIChmIGluIGZpbHRybyl7DQojICAgZm9yIChuIGluIG5pdmVsKXsNCiMgICAgIGZvciAodSBpbiB1bWJyYWwpew0KIyAgICAgICBjYW5hbDEgPC0gZGVub2lzZS5kd3QuMmQoaV9ydWlkb3NhWywsMV0sIHdmID0gZiwgcnVsZSA9IHUsIEogPSBuKQ0KIyAgICAgICBjYW5hbDIgPC0gZGVub2lzZS5kd3QuMmQoaV9ydWlkb3NhWywsMl0sIHdmID0gZiwgcnVsZSA9IHUsIEogPSBuKQ0KIyAgICAgICBjYW5hbDMgPC0gZGVub2lzZS5kd3QuMmQoaV9ydWlkb3NhWywsM10sIHdmID0gZiwgcnVsZSA9IHUsIEogPSBuKQ0KIyAgICAgICBpX2Rlbm9pc2VkIDwtIGFycmF5KGMoY2FuYWwxLCBjYW5hbDIsIGNhbmFsMyksIGRpbSA9IGMobnJvdyhjYW5hbDEpLCBuY29sKGNhbmFsMSksIDMpKQ0KIyAgICAgICAgIA0KIyAgICAgICBkaXNwbGF5KEltYWdlKGlfZGVub2lzZWQsIGNvbG9ybW9kZSA9ICJDb2xvciIpLCBtZXRob2QgPSAiciIpDQojICAgICAgIHRpdGxlKHBhc3RlMCgiV2F2ZWxldCAiLCBmLCAiLCB1bWJyYWwgIiAsIHUsICIsIG5pdmVsICIsIG4pKQ0KIyAgIH19fQ0KIyANCiMgZGV2Lm9mZigpDQoNCmluY2x1ZGVfZ3JhcGhpY3MoImRlbm9pc2Vkd3QyZC9wZXBwZXIucG5nIikNCmluY2x1ZGVfZ3JhcGhpY3MoImRlbm9pc2Vkd3QyZC9wZXBwZXJfZGVub2lzZWQucG5nIikNCg0KYGBgDQpFbiBlc3RlIGNhc28sIGRlIG51ZXZvIGxhcyBkaWZlcmVuY2lhcyBtw6FzIG5vdGFibGVzIGRlIGRlYmVuIGEgbG9zIGRpc3RpbnRvcyBuaXZlbGVzIGRlIGRlc2NvbXBvc2ljacOzbiwgc2luIGVtYmFyZ28sIHRhbWJpw6luIG9ic2VydmFtb3MgZGlmZXJlbmNpYXMgc3V0aWxlcyBlbiBsb3MgcmVzdWx0YWRvcyBlbiBjdWFudG8gYToNCg0KKiBMYSByZWdsYSBkZSBhcGxpY2FjacOzbiBkZWwgdW1icmFsOiBBbCB1dGlsaXphciBsYSByZWdsYSBzb2Z0LCBzZSBjb25zaWd1ZSB1bmEgbWVqb3IgZWxpbWluYWNpw7NuIGRlbCBydWlkby4gRXN0byBwb2Ryw61hIGRlYmVyc2UgYSBxdWUgZWwgcnVpZG8gc2FsdCBhbmQgcGVwcGVyIGFzaWduYSB2YWxvcmVzIGNlcmNhbm9zIGEgMCAocGltaWVudGEsIG5lZ3JvKSB5IGNlcmNhbm9zIGEgMSAoc2FsLCBibGFuY28pIGEgYWxndW5vcyBww614ZWxlcy4gQWwgYXBsaWNhciBlbCBtw6l0b2RvIGhhcmQsIG5vIHNlIGF0ZW7DumFuIGFkZWN1YWRhbWVudGUgbG9zIHBpY29zIGRlIHJ1aWRvIGRlYmlkbyBhIHN1IG5hdHVyYWxlemEgbcOhcyBhZ3Jlc2l2YS4NCg0KKiBFbCBmaWx0cm8gd2F2ZWxldCBlbXBsZWFkbzogRWwgcmVuZGltaWVudG8gY29uIGVsIGZpbHRybyBkNCBlcyBpbmZlcmlvciBhbCBkZSBvdHJvcyBmaWx0cm9zLiBFc3RvIHNlIGRlYmUgYSBxdWUgZWwgZmlsdHJvIGQ0IGVzIHVuIGZpbHRybyBjb3J0byAoY29uIHNvbG8gNCBjb2VmaWNpZW50ZXMpIHkgdGllbmUgdW5hIHJlc29sdWNpw7NuIGRlIGZyZWN1ZW5jaWEgbGltaXRhZGEuIENvbW8gcmVzdWx0YWRvLCBubyBwdWVkZSBjYXB0dXJhciBlZmljYXptZW50ZSBsb3MgcGljb3MgYWJydXB0b3MgZGVsIHJ1aWRvLCBjb21vIGxvcyB2YWxvcmVzIDAgeSAxIGRlbCBydWlkbyBzYWx0IGFuZCBwZXBwZXIuDQoNCmBgYHtyLCBlY2hvID0gRkFMU0V9DQojIHBuZygiZGVub2lzZWR3dDJkL2dhbW1hLnBuZyIsIHdpZHRoID0gODAwLCBoZWlnaHQgPSA0MDApDQojIA0KIyBpX3NpbnJ1aWRvIDwtIGltYWdlc1tbMV1dDQojIGlfcnVpZG9zYSA8LSBhZGRfbm9pc2VfdG9faW1hZ2UoIjEiLCAiZ2FtbWEiLCBsaXN0KGxvb2tzID0gMikpDQojIA0KIyBpX2RpbSA8LSBkaW0oaV9zaW5ydWlkbykNCiMgaV9sZW4gPC0gbGVuZ3RoKGlfc2lucnVpZG8pDQojIA0KIyBpX2Rlbm9pc2VkIDwtIGFycmF5KDAsIGRpbSA9IGRpbShpX3J1aWRvc2EpKQ0KIyANCiMgcGFyKG1mcm93PSBjKDEsMikpDQojIGRpc3BsYXkoSW1hZ2UoaV9zaW5ydWlkbywgY29sb3Jtb2RlID0gIkNvbG9yIiksIG1ldGhvZCA9ICJyIikNCiMgdGl0bGUoIk9yaWdpbmFsIikNCiMgZGlzcGxheShJbWFnZShpX3J1aWRvc2EsIGNvbG9ybW9kZSA9ICJDb2xvciIpLCBtZXRob2QgPSAiciIpDQojIHRpdGxlKCJJbWFnZW4gY29uIHJ1aWRvIikNCiMgDQojIGRldi5vZmYoKQ0KIyBwbmcoImRlbm9pc2Vkd3QyZC9nYW1tYV9kZW5vaXNlZC5wbmciLCB3aWR0aCA9IDEyMDAsIGhlaWdodCA9IDE4MDApDQojIA0KIyBwYXIobWZjb2w9IGMoNiw0KSkNCiMgZm9yIChmIGluIGZpbHRybyl7DQojICAgZm9yIChuIGluIG5pdmVsKXsNCiMgICAgIGZvciAodSBpbiB1bWJyYWwpew0KIyAgICAgICBjYW5hbDEgPC0gZGVub2lzZS5kd3QuMmQoaV9ydWlkb3NhWywsMV0sIHdmID0gZiwgcnVsZSA9IHUsIEogPSBuKQ0KIyAgICAgICBjYW5hbDIgPC0gZGVub2lzZS5kd3QuMmQoaV9ydWlkb3NhWywsMl0sIHdmID0gZiwgcnVsZSA9IHUsIEogPSBuKQ0KIyAgICAgICBjYW5hbDMgPC0gZGVub2lzZS5kd3QuMmQoaV9ydWlkb3NhWywsM10sIHdmID0gZiwgcnVsZSA9IHUsIEogPSBuKQ0KIyAgICAgICBpX2Rlbm9pc2VkIDwtIGFycmF5KGMoY2FuYWwxLCBjYW5hbDIsIGNhbmFsMyksIGRpbSA9IGMobnJvdyhjYW5hbDEpLCBuY29sKGNhbmFsMSksIDMpKQ0KIyANCiMgICAgICAgZGlzcGxheShJbWFnZShpX2Rlbm9pc2VkLCBjb2xvcm1vZGUgPSAiQ29sb3IiKSwgbWV0aG9kID0gInIiKQ0KIyAgICAgICB0aXRsZShwYXN0ZTAoIldhdmVsZXQgIiwgZiwgIiwgdW1icmFsICIgLCB1LCAiLCBuaXZlbCAiLCBuKSkNCiMgICB9fX0NCiMgDQojIGRldi5vZmYoKQ0KIyANCiMgaW5jbHVkZV9ncmFwaGljcygiZGVub2lzZWR3dDJkL2dhbW1hLnBuZyIpDQojIGluY2x1ZGVfZ3JhcGhpY3MoImRlbm9pc2Vkd3QyZC9nYW1tYV9kZW5vaXNlZC5wbmciKQ0KDQpgYGANCg0KYGBge3IsIGVjaG8gPSBGQUxTRX0NCiMgcG5nKCJkZW5vaXNlZHd0MmQvbXVsdGlwbGljYXRpdmUucG5nIiwgd2lkdGggPSA4MDAsIGhlaWdodCA9IDQwMCkNCiMgDQojIGlfc2lucnVpZG8gPC0gaW1hZ2VzW1syXV0NCiMgaV9ydWlkb3NhIDwtIGFkZF9ub2lzZV90b19pbWFnZSgiMiIsICJ1bmlmb3JtX211bHRpcGxpY2F0aXZlIiwgbGlzdChsb29rcyA9IDIpKQ0KIyANCiMgaV9kaW0gPC0gZGltKGlfc2lucnVpZG8pDQojIGlfbGVuIDwtIGxlbmd0aChpX3NpbnJ1aWRvKQ0KIyANCiMgaV9kZW5vaXNlZCA8LSBhcnJheSgwLCBkaW0gPSBkaW0oaV9ydWlkb3NhKSkNCiMgDQojIHBhcihtZnJvdz0gYygxLDIpKQ0KIyBkaXNwbGF5KEltYWdlKGlfc2lucnVpZG8sIGNvbG9ybW9kZSA9ICJDb2xvciIpLCBtZXRob2QgPSAiciIpDQojIHRpdGxlKCJPcmlnaW5hbCIpDQojIGRpc3BsYXkoSW1hZ2UoaV9ydWlkb3NhLCBjb2xvcm1vZGUgPSAiQ29sb3IiKSwgbWV0aG9kID0gInIiKQ0KIyB0aXRsZSgiSW1hZ2VuIGNvbiBydWlkbyIpDQojIA0KIyBkZXYub2ZmKCkNCiMgcG5nKCJkZW5vaXNlZHd0MmQvbXVsdGlwbGljYXRpdmVfZGVub2lzZWQucG5nIiwgd2lkdGggPSAxMjAwLCBoZWlnaHQgPSAxODAwKQ0KIyANCiMgcGFyKG1mY29sPSBjKDYsNCkpDQojIGZvciAoZiBpbiBmaWx0cm8pew0KIyAgIGZvciAobiBpbiBuaXZlbCl7DQojICAgICBmb3IgKHUgaW4gdW1icmFsKXsNCiMgICAgICAgY2FuYWwxIDwtIGRlbm9pc2UuZHd0LjJkKGlfcnVpZG9zYVssLDFdLCB3ZiA9IGYsIHJ1bGUgPSB1LCBKID0gbikNCiMgICAgICAgY2FuYWwyIDwtIGRlbm9pc2UuZHd0LjJkKGlfcnVpZG9zYVssLDJdLCB3ZiA9IGYsIHJ1bGUgPSB1LCBKID0gbikNCiMgICAgICAgY2FuYWwzIDwtIGRlbm9pc2UuZHd0LjJkKGlfcnVpZG9zYVssLDNdLCB3ZiA9IGYsIHJ1bGUgPSB1LCBKID0gbikNCiMgICAgICAgaV9kZW5vaXNlZCA8LSBhcnJheShjKGNhbmFsMSwgY2FuYWwyLCBjYW5hbDMpLCBkaW0gPSBjKG5yb3coY2FuYWwxKSwgbmNvbChjYW5hbDEpLCAzKSkNCiMgDQojICAgICAgIGRpc3BsYXkoSW1hZ2UoaV9kZW5vaXNlZCwgY29sb3Jtb2RlID0gIkNvbG9yIiksIG1ldGhvZCA9ICJyIikNCiMgICAgICAgdGl0bGUocGFzdGUwKCJXYXZlbGV0ICIsIGYsICIsIHVtYnJhbCAiICwgdSwgIiwgbml2ZWwgIiwgbikpDQojICAgfX19DQojIA0KIyBkZXYub2ZmKCkNCiMgDQojIGluY2x1ZGVfZ3JhcGhpY3MoImRlbm9pc2Vkd3QyZC9tdWx0aXBsaWNhdGl2ZS5wbmciKQ0KIyBpbmNsdWRlX2dyYXBoaWNzKCJkZW5vaXNlZHd0MmQvbXVsdGlwbGljYXRpdmVfZGVub2lzZWQucG5nIikNCg0KYGBgDQoNClBvciDDumx0aW1vLCBwcmVzZW50YW1vcyBsb3MgcmVzdWx0YWRvcyBkZWwgcHJvY2VzbyBkZSBkZW5vaXNpbmcgZGUgbGEgaW1hZ2VuIDUsIGEgbGEgcXVlIHNlIGxlIGhhIGFwbGljYWRvIHJ1aWRvIGdhdXNzaWFuby4gQSBkaWZlcmVuY2lhIGRlIGxhIGltYWdlbiAxLCBlbiBlc3RlIGNhc28gbGEgcmVzb2x1Y2nDs24gZGUgbGEgaW1hZ2VuIGVzIGNvbnNpZGVyYWJsZW1lbnRlIGluZmVyaW9yLCBsbyBxdWUgcGFyZWNlIGluZmx1aXIgZW4gbG9zIHJlc3VsdGFkb3Mgb2J0ZW5pZG9zLg0KDQpgYGB7cn0NCg0KIyBwbmcoImRlbm9pc2Vkd3QyZC9pbWFnZTVfbm9pc2UucG5nIiwgd2lkdGggPSA4MDAsIGhlaWdodCA9IDQwMCkNCiMgDQojIGlfc2lucnVpZG8gPC0gaW1hZ2VzW1s1XV0NCiMgaV9ydWlkb3NhIDwtIGFkZF9ub2lzZV90b19pbWFnZSgiNSIsICJnYXVzc2lhbiIsIGxpc3Qoc3RkX2RldiA9IDAuMykpDQojICAgICANCiMgaV9kaW0gPC0gZGltKGlfc2lucnVpZG8pDQojIGlfbGVuIDwtIGxlbmd0aChpX3NpbnJ1aWRvKQ0KIyANCiMgaV9kZW5vaXNlZCA8LSBhcnJheSgwLCBkaW0gPSBkaW0oaV9ydWlkb3NhKSkNCiMgDQojIHBhcihtZnJvdz0gYygxLDIpKQ0KIyBkaXNwbGF5KEltYWdlKGlfc2lucnVpZG8sIGNvbG9ybW9kZSA9ICJDb2xvciIpLCBtZXRob2QgPSAiciIpDQojIHRpdGxlKCJPcmlnaW5hbCIpDQojIGRpc3BsYXkoSW1hZ2UoaV9ydWlkb3NhLCBjb2xvcm1vZGUgPSAiQ29sb3IiKSwgbWV0aG9kID0gInIiKQ0KIyB0aXRsZSgiSW1hZ2VuIGNvbiBydWlkbyIpDQojIA0KIyBkZXYub2ZmKCkNCiMgcG5nKCJkZW5vaXNlZHd0MmQvaW1hZ2U1X2Rlbm9pc2VkLnBuZyIsIHdpZHRoID0gMTIwMCwgaGVpZ2h0ID0gMTgwMCkNCiMgDQojIHBhcihtZmNvbD0gYyg2LDQpKQ0KIyBmb3IgKGYgaW4gZmlsdHJvKXsNCiMgICBmb3IgKG4gaW4gbml2ZWwpew0KIyAgICAgZm9yICh1IGluIHVtYnJhbCl7DQojICAgICAgIGNhbmFsMSA8LSBkZW5vaXNlLmR3dC4yZChpX3J1aWRvc2FbLCwxXSwgd2YgPSBmLCBydWxlID0gdSwgSiA9IG4pDQojICAgICAgIGNhbmFsMiA8LSBkZW5vaXNlLmR3dC4yZChpX3J1aWRvc2FbLCwyXSwgd2YgPSBmLCBydWxlID0gdSwgSiA9IG4pDQojICAgICAgIGNhbmFsMyA8LSBkZW5vaXNlLmR3dC4yZChpX3J1aWRvc2FbLCwzXSwgd2YgPSBmLCBydWxlID0gdSwgSiA9IG4pDQojICAgICAgIGlfZGVub2lzZWQgPC0gYXJyYXkoYyhjYW5hbDEsIGNhbmFsMiwgY2FuYWwzKSwgZGltID0gYyhucm93KGNhbmFsMSksIG5jb2woY2FuYWwxKSwgMykpDQojICAgICAgICAgDQojICAgICAgIGRpc3BsYXkoSW1hZ2UoaV9kZW5vaXNlZCwgY29sb3Jtb2RlID0gIkNvbG9yIiksIG1ldGhvZCA9ICJyIikNCiMgICAgICAgdGl0bGUocGFzdGUwKCJXYXZlbGV0ICIsIGYsICIsIHVtYnJhbCAiICwgdSwgIiwgbml2ZWwgIiwgbikpDQojICAgfX19DQojIA0KIyBkZXYub2ZmKCkNCg0KaW5jbHVkZV9ncmFwaGljcygiZGVub2lzZWR3dDJkL2ltYWdlNV9ub2lzZS5wbmciKQ0KaW5jbHVkZV9ncmFwaGljcygiZGVub2lzZWR3dDJkL2ltYWdlNV9kZW5vaXNlZC5wbmciKQ0KDQpgYGANCg0KTGEgZGlmZXJlbmNpYSBtw6FzIG5vdGFibGUgZW4gY29tcGFyYWNpw7NuIGNvbiBlbCByZXN0byBkZSByZXN1bHRhZG9zLCBlcyBxdWUgZW4gZXN0ZSBjYXNvLCBhbCBhdW1lbnRhciBlbCBudW1lcm8gZGUgbml2ZWxlcyBkZSBkZXNjb21wb3NpY2nDs24gYSB1biBuw7ptZXJvIHF1ZSBub3MgcGVybWl0YSBlbGltaW5hciBsYSBtYXlvciBwYXJ0ZSBkZWwgcnVpZG8sIGxhIGNhbGlkYWQgZGUgbGEgaW1hZ2VuIHNlIHZlIHNpZ25pZmljYXRpdmFtZW50ZSBhZmVjdGFkYSwgeSBvYnRlbmVtb3MgdW5hIGltYWdlbiBleGNlc2l2YW1lbnRlIHN1YXZpemFkYS4gRXN0byBwdWVkZSBkZWJlcnNlIGEgcXVlIGEgbWVkaWRhIHF1ZSBhdW1lbnRhbiBsb3Mgbml2ZWxlcyBkZSBkZXNjb21wb3NpY2nDs24sIGxhIGltYWdlbiBzZSBkZXNjb21wb25lIGVuIGZyZWN1ZW5jaWFzIGNhZGEgdmV6IG3DoXMgYWx0YXMsIHByb3ZvY2FuZG8gbGEgcMOpcmRpZGEgZGUgZGV0YWxsZXMgZmlub3MuDQoNCkxhIGJhamEgcmVzb2x1Y2nDs24gZGUgbGEgaW1hZ2VuIGhhY2UgcXVlIHNlYSBtw6FzIGRpZsOtY2lsIG1hbnRlbmVyIHVuIGVxdWlsaWJyaW8gZW50cmUgbGEgZWxpbWluYWNpw7NuIGRlbCBydWlkbyB5IGxhIHByZXNlcnZhY2nDs24gZGUgbG9zIGRldGFsbGVzLiBBbCBhdW1lbnRhciBsb3Mgbml2ZWxlcyBkZSBkZXNjb21wb3NpY2nDs24sIGVsIHJ1aWRvIHNlIGVsaW1pbmEgZW4gbWF5b3IgbWVkaWRhLCBwZXJvIHRhbWJpw6luIHNlIHBpZXJkZSBtdWNoYSBpbmZvcm1hY2nDs24gw7p0aWwuDQpgYGB7ciwgZWNobyA9IEZBTFNFfQ0Kcm0oZmlsdHJvLCBuaXZlbCwgdW1icmFsLCB3YXZlbHRfZmlsdGVyKQ0KYGBgDQoNCg0KIyMgRnVuY2nDs24gZmZ0c2hpZnQNCg0KUG9yIMO6bHRpbW8sIHZhbW9zIGEgdmVyIHF1ZSBlcyBsbyBxdWUgb2N1cnJlIGN1YW5kbyBlbiBsdWdhciBkZSB0cmFuc2Zvcm1hZGFzIHdhdmVsZXQgZW1wbGVhbW9zIHRyYW5zZm9ybWFkYXMgZGUgZm91cmllci4NCmBgYHtyfQ0KaW1ncl8xIDwtIGFkZF9ub2lzZV90b19pbWFnZSgiMSIsICJnYXVzc2lhbiIsIGxpc3Qoc3RkX2RldiA9IDAuMykpDQppbWdyXzIgPC0gYWRkX25vaXNlX3RvX2ltYWdlKCIyIiwgInNpbnVzb2lkYWxfaGlnaCIsIGxpc3QoZnJlcXVlbmN5ID0gMjUsIGFtcGxpdHVkZSA9IDAuMikpDQppbWdyXzMgPC0gYWRkX25vaXNlX3RvX2ltYWdlKCIzIiwgInNpbnVzb2lkYWxfbG93IiwgbGlzdChmcmVxdWVuY3kgPSAyLCBhbXBsaXR1ZGUgPSAwLjIpKQ0KaW1ncl80IDwtIGFkZF9ub2lzZV90b19pbWFnZSgiNCIsICJzYWx0X3BlcHBlciIsIGxpc3QoZXBzaWxvbiA9IDAuMSkpDQppbWdyXzUgPC0gYWRkX25vaXNlX3RvX2ltYWdlKCI1IiwgImdhbW1hIiwgbGlzdChsb29rcyA9IDIpKQ0KaW1ncl82IDwtIGFkZF9ub2lzZV90b19pbWFnZSgiNSIsICJ1bmlmb3JtX211bHRpcGxpY2F0aXZlIiwgbGlzdChsb29rcyA9IDIpKQ0KYGBgDQoNCg0KYGBge3IsIGVjaG89Rn0NCnBfbG9hZChnc2lnbmFsLCBmaWVsZHMpDQpgYGANCg0KIyMjIEltYWdlbiAxIHkgcnVpZG8gZ2F1c3NpYW5vDQoNCg0KYGBge3IsIGVjaG89Rn0NCiMgQ0FOQUwgUk9KTw0KDQpmZnQxX3IgPC0gZmZ0KGltZ3JfMVssLDFdKQ0KbjFfciA8LSBsZW5ndGgoZmZ0MV9yKQ0KDQpmc2hpZnQxX3IgPC0gZ3NpZ25hbDo6ZmZ0c2hpZnQoZmZ0MV9yLCBNQVJHSU4gPSBjKDEsMikpDQpzcGVjdHIxX3IgPC0gMjAqbG9nMTAoTW9kKGZzaGlmdDFfcikpDQoNCiMgZmllbGRzOjppbWFnZS5wbG90KHNwZWN0cjFfcikNCg0KIyBDQU5BTCBWRVJERQ0KDQpmZnQxX2cgPC0gZmZ0KGltZ3JfMVssLDJdKQ0KbjFfZyA8LSBsZW5ndGgoZmZ0MV9nKQ0KDQpmc2hpZnQxX2cgPC0gZ3NpZ25hbDo6ZmZ0c2hpZnQoZmZ0MV9nLCBNQVJHSU4gPSBjKDEsMikpDQpzcGVjdHIxX2cgPC0gMjAqbG9nMTAoTW9kKGZzaGlmdDFfZykpDQoNCiMgZmllbGRzOjppbWFnZS5wbG90KHNwZWN0cjFfZykNCg0KIyBDQU5BTCBBWlVMDQoNCmZmdDFfYiA8LSBmZnQoaW1ncl8xWywsM10pDQpuMV9iIDwtIGxlbmd0aChmZnQxX2IpDQoNCmZzaGlmdDFfYiA8LSBnc2lnbmFsOjpmZnRzaGlmdChmZnQxX2IsIE1BUkdJTiA9IGMoMSwyKSkNCnNwZWN0cjFfYiA8LSAyMCpsb2cxMChNb2QoZnNoaWZ0MV9iKSkNCg0KIyBmaWVsZHM6OmltYWdlLnBsb3Qoc3BlY3RyMV9iKQ0KYGBgDQoNCmBgYHtyLCBlY2hvPUZ9DQoNCiMgQ0FOQUwgUk9KTw0KDQpzbWFsbDFfciA8LSAxMA0KDQpmc2hpZnQxX3JbMDo2MDAwLCAwOjEyMDBdIDwtIHNtYWxsMV9yDQpmc2hpZnQxX3JbMDo2MDAwLCAyODAwOjQwMDBdIDwtIHNtYWxsMV9yDQpmc2hpZnQxX3JbMDoyNTAwLCAxMjAwOjI4MDBdIDwtIHNtYWxsMV9yDQpmc2hpZnQxX3JbMzUwMDo2MDAwLCAxMjAwOjI4MDBdIDwtIHNtYWxsMV9yDQoNCnNwZWN0cl9zaGlmdGVkMV9yIDwtIDIwKmxvZzEwKE1vZChmc2hpZnQxX3IpKQ0KDQojIGZpZWxkczo6aW1hZ2UucGxvdChzcGVjdHJfc2hpZnRlZDFfcikNCg0KIyBDQU5BTCBWRVJERQ0KDQpzbWFsbDFfZyA8LSAxMA0KDQpmc2hpZnQxX2dbMDo2MDAwLCAwOjEyMDBdIDwtIHNtYWxsMV9nDQpmc2hpZnQxX2dbMDo2MDAwLCAyODAwOjQwMDBdIDwtIHNtYWxsMV9nDQpmc2hpZnQxX2dbMDoyNTAwLCAxMjAwOjI4MDBdIDwtIHNtYWxsMV9nDQpmc2hpZnQxX2dbMzUwMDo2MDAwLCAxMjAwOjI4MDBdIDwtIHNtYWxsMV9nDQoNCnNwZWN0cl9zaGlmdGVkMV9nIDwtIDIwKmxvZzEwKE1vZChmc2hpZnQxX2cpKQ0KDQojIGZpZWxkczo6aW1hZ2UucGxvdChzcGVjdHJfc2hpZnRlZDFfZykNCg0KIyBDQU5BTCBBWlVMDQoNCnNtYWxsMV9iIDwtIDEwDQoNCmZzaGlmdDFfYlswOjYwMDAsIDA6MTIwMF0gPC0gc21hbGwxX2INCmZzaGlmdDFfYlswOjYwMDAsIDI4MDA6NDAwMF0gPC0gc21hbGwxX2INCmZzaGlmdDFfYlswOjI1MDAsIDEyMDA6MjgwMF0gPC0gc21hbGwxX2INCmZzaGlmdDFfYlszNTAwOjYwMDAsIDEyMDA6MjgwMF0gPC0gc21hbGwxX2INCg0Kc3BlY3RyX3NoaWZ0ZWQxX2IgPC0gMjAqbG9nMTAoTW9kKGZzaGlmdDFfYikpDQoNCiMgZmllbGRzOjppbWFnZS5wbG90KHNwZWN0cl9zaGlmdGVkMV9iKQ0KDQpgYGANCg0KYGBge3IsIGVjaG89Rn0NCmZmdDFtb2RfciA8LSBnc2lnbmFsOjppZmZ0c2hpZnQoZnNoaWZ0MV9yLCBNQVJHSU4gPSBjKDEsMikpDQppbWdfMV9yIDwtIFJlKGZmdChmZnQxbW9kX3IsIGludmVyc2UgPSBUKSkgLyBwcm9kKGRpbShmZnQxbW9kX3IpKQ0KZmZ0MW1vZF9nIDwtIGdzaWduYWw6OmlmZnRzaGlmdChmc2hpZnQxX2csIE1BUkdJTiA9IGMoMSwyKSkNCmltZ18xX2cgPC0gUmUoZmZ0KGZmdDFtb2RfZywgaW52ZXJzZSA9IFQpKSAvIHByb2QoZGltKGZmdDFtb2RfZykpDQpmZnQxbW9kX2IgPC0gZ3NpZ25hbDo6aWZmdHNoaWZ0KGZzaGlmdDFfYiwgTUFSR0lOID0gYygxLDIpKQ0KaW1nXzFfYiA8LSBSZShmZnQoZmZ0MW1vZF9iLCBpbnZlcnNlID0gVCkpIC8gcHJvZChkaW0oZmZ0MW1vZF9iKSkNCg0KaW1nXzEgPC0gYXJyYXkoMCwgZGltID0gZGltKGltZ3JfMSkpDQppbWdfMVssLDFdIDwtIGltZ18xX3INCmltZ18xWywsMl0gPC0gaW1nXzFfZw0KaW1nXzFbLCwzXSA8LSBpbWdfMV9iDQoNCmBgYA0KDQoNCmBgYHtyIGVjaG89Rn0NCnBsb3QoSW1hZ2UoKGltZ3JfMSksIGNvbG9ybW9kZSA9ICJDb2xvciIpKQ0KcGxvdChJbWFnZSgoaW1nXzEpLCBjb2xvcm1vZGUgPSAiQ29sb3IiKSkNCmBgYA0KDQojIyMgSW1hZ2VuIDIgeSBydWlkbyBzaW51c29pZGFsIGFsdG8gDQoNCg0KYGBge3IgZWNobz1GfQ0KDQojIENBTkFMIFJPSk8NCg0KZmZ0Ml9yIDwtIGZmdChpbWdyXzJbLCwxXSkNCm4yX3IgPC0gbGVuZ3RoKGZmdDJfcikNCg0KZnNoaWZ0Ml9yIDwtIGdzaWduYWw6OmZmdHNoaWZ0KGZmdDJfciwgTUFSR0lOID0gYygxLDIpKQ0Kc3BlY3RyMl9yIDwtIDIwKmxvZzEwKE1vZChmc2hpZnQyX3IpKQ0KDQojIGZpZWxkczo6aW1hZ2UucGxvdChzcGVjdHIyX3IpDQoNCiMgQ0FOQUwgVkVSREUNCg0KZmZ0Ml9nIDwtIGZmdChpbWdyXzJbLCwyXSkNCm4yX2cgPC0gbGVuZ3RoKGZmdDJfZykNCg0KZnNoaWZ0Ml9nIDwtIGdzaWduYWw6OmZmdHNoaWZ0KGZmdDJfZywgTUFSR0lOID0gYygxLDIpKQ0Kc3BlY3RyMl9nIDwtIDIwKmxvZzEwKE1vZChmc2hpZnQyX2cpKQ0KDQojIGZpZWxkczo6aW1hZ2UucGxvdChzcGVjdHIyX2cpDQoNCiMgQ0FOQUwgQVpVTA0KDQpmZnQyX2IgPC0gZmZ0KGltZ3JfMlssLDNdKQ0KbjJfYiA8LSBsZW5ndGgoZmZ0Ml9iKQ0KDQpmc2hpZnQyX2IgPC0gZ3NpZ25hbDo6ZmZ0c2hpZnQoZmZ0Ml9iLCBNQVJHSU4gPSBjKDEsMikpDQpzcGVjdHIyX2IgPC0gMjAqbG9nMTAoTW9kKGZzaGlmdDJfYikpDQoNCiMgZmllbGRzOjppbWFnZS5wbG90KHNwZWN0cjJfYikNCmBgYA0KDQpgYGB7ciBlY2hvPUZ9DQoNCiMgQ0FOQUwgUk9KTw0KDQpzbWFsbDJfciA8LSAtMQ0KDQpmc2hpZnQyX3JbMjk4MDozMDIwLCAwOjgwMF0gPC0gc21hbGwyX3INCmZzaGlmdDJfclsyOTgwOjMwMjAsIDMyMDA6NDAwMF0gPC0gc21hbGwyX3INCmZzaGlmdDJfclsyOTAwOjMxMDAsIDA6MTAwXSA8LSBzbWFsbDJfcg0KZnNoaWZ0Ml9yWzI5MDA6MzEwMCwgMzkwMDo0MDAwXSA8LSBzbWFsbDJfcg0KDQpzcGVjdHJfc2hpZnRlZDJfciA8LSAyMCpsb2cxMChNb2QoZnNoaWZ0Ml9yKSkNCg0KIyBmaWVsZHM6OmltYWdlLnBsb3Qoc3BlY3RyX3NoaWZ0ZWQyX3IpDQoNCiMgQ0FOQUwgVkVSREUNCg0Kc21hbGwyX2cgPC0gLTENCg0KZnNoaWZ0Ml9nWzI5ODA6MzAyMCwgMDo4MDBdIDwtIHNtYWxsMl9nDQpmc2hpZnQyX2dbMjk4MDozMDIwLCAzMjAwOjQwMDBdIDwtIHNtYWxsMl9nDQpmc2hpZnQyX2dbMjkwMDozMTAwLCAwOjEwMF0gPC0gc21hbGwyX2cNCmZzaGlmdDJfZ1syOTAwOjMxMDAsIDM5MDA6NDAwMF0gPC0gc21hbGwyX2cNCg0Kc3BlY3RyX3NoaWZ0ZWQyX2cgPC0gMjAqbG9nMTAoTW9kKGZzaGlmdDJfZykpDQoNCiMgZmllbGRzOjppbWFnZS5wbG90KHNwZWN0cl9zaGlmdGVkMl9nKQ0KDQojIENBTkFMIEFaVUwNCg0Kc21hbGwyX2IgPC0gLTENCg0KZnNoaWZ0Ml9iWzI5ODA6MzAyMCwgMDo4MDBdIDwtIHNtYWxsMl9iDQpmc2hpZnQyX2JbMjk4MDozMDIwLCAzMjAwOjQwMDBdIDwtIHNtYWxsMl9iDQpmc2hpZnQyX2JbMjkwMDozMTAwLCAwOjEwMF0gPC0gc21hbGwyX2INCmZzaGlmdDJfYlsyOTAwOjMxMDAsIDM5MDA6NDAwMF0gPC0gc21hbGwyX2INCg0Kc3BlY3RyX3NoaWZ0ZWQyX2IgPC0gMjAqbG9nMTAoTW9kKGZzaGlmdDJfYikpDQoNCiMgZmllbGRzOjppbWFnZS5wbG90KHNwZWN0cl9zaGlmdGVkMl9iKQ0KDQpgYGANCg0KYGBge3IgZWNobz1GfQ0KZmZ0Mm1vZF9yIDwtIGdzaWduYWw6OmlmZnRzaGlmdChmc2hpZnQyX3IsIE1BUkdJTiA9IGMoMSwyKSkNCmltZ18yX3IgPC0gUmUoZmZ0KGZmdDJtb2RfciwgaW52ZXJzZSA9IFQpKSAvIHByb2QoZGltKGZmdDJtb2RfcikpDQpmZnQybW9kX2cgPC0gZ3NpZ25hbDo6aWZmdHNoaWZ0KGZzaGlmdDJfZywgTUFSR0lOID0gYygxLDIpKQ0KaW1nXzJfZyA8LSBSZShmZnQoZmZ0Mm1vZF9nLCBpbnZlcnNlID0gVCkpIC8gcHJvZChkaW0oZmZ0Mm1vZF9nKSkNCmZmdDJtb2RfYiA8LSBnc2lnbmFsOjppZmZ0c2hpZnQoZnNoaWZ0Ml9iLCBNQVJHSU4gPSBjKDEsMikpDQppbWdfMl9iIDwtIFJlKGZmdChmZnQybW9kX2IsIGludmVyc2UgPSBUKSkgLyBwcm9kKGRpbShmZnQybW9kX2IpKQ0KDQppbWdfMiA8LSBhcnJheSgwLCBkaW0gPSBkaW0oaW1ncl8yKSkNCmltZ18yWywsMV0gPC0gaW1nXzJfcg0KaW1nXzJbLCwyXSA8LSBpbWdfMl9nDQppbWdfMlssLDNdIDwtIGltZ18yX2INCg0KYGBgDQoNCg0KYGBge3IgZWNobz1GfQ0KcGxvdChJbWFnZSgoaW1ncl8yKSwgY29sb3Jtb2RlID0gIkNvbG9yIikpDQpwbG90KEltYWdlKChpbWdfMiksIGNvbG9ybW9kZSA9ICJDb2xvciIpKQ0KYGBgDQoNCg0KIyMjIEltYWdlbiAzIHkgcnVpZG8gc2ludXNvaWRhbCBiYWpvDQoNCg0KYGBge3IgZWNobz1GfQ0KIyBDQU5BTCBST0pPDQoNCmZmdDNfciA8LSBmZnQoaW1ncl8zWywsMV0pDQpuM19yIDwtIGxlbmd0aChmZnQzX3IpDQoNCmZzaGlmdDNfciA8LSBnc2lnbmFsOjpmZnRzaGlmdChmZnQzX3IsIE1BUkdJTiA9IGMoMSwyKSkNCnNwZWN0cjNfciA8LSAyMCpsb2cxMChNb2QoZnNoaWZ0M19yKSkNCg0KIyBmaWVsZHM6OmltYWdlLnBsb3Qoc3BlY3RyM19yKQ0KDQojIENBTkFMIFZFUkRFDQoNCmZmdDNfZyA8LSBmZnQoaW1ncl8zWywsMl0pDQpuM19nIDwtIGxlbmd0aChmZnQzX2cpDQoNCmZzaGlmdDNfZyA8LSBnc2lnbmFsOjpmZnRzaGlmdChmZnQzX2csIE1BUkdJTiA9IGMoMSwyKSkNCnNwZWN0cjNfZyA8LSAyMCpsb2cxMChNb2QoZnNoaWZ0M19nKSkNCg0KIyBmaWVsZHM6OmltYWdlLnBsb3Qoc3BlY3RyM19nKQ0KDQojIENBTkFMIEFaVUwNCg0KZmZ0M19iIDwtIGZmdChpbWdyXzNbLCwzXSkNCm4zM19iIDwtIGxlbmd0aChmZnQzX2IpDQoNCmZzaGlmdDNfYiA8LSBnc2lnbmFsOjpmZnRzaGlmdChmZnQzX2IsIE1BUkdJTiA9IGMoMSwyKSkNCnNwZWN0cjNfYiA8LSAyMCpsb2cxMChNb2QoZnNoaWZ0M19iKSkNCg0KIyBmaWVsZHM6OmltYWdlLnBsb3Qoc3BlY3RyM19iKQ0KYGBgDQoNCmBgYHtyIGVjaG89Rn0NCg0KIyBDQU5BTCBST0pPDQoNCnNtYWxsM19yIDwtIDEwDQoNCmZzaGlmdDNfclswOjYwMDAsIDA6MTIwMF0gPC0gc21hbGwzX3INCmZzaGlmdDNfclswOjYwMDAsIDI4MDA6NDAwMF0gPC0gc21hbGwzX3INCmZzaGlmdDNfclswOjI1MDAsIDEyMDA6MjgwMF0gPC0gc21hbGwzX3INCmZzaGlmdDNfclszNTAwOjYwMDAsIDEyMDA6MjgwMF0gPC0gc21hbGwzX3INCg0Kc3BlY3RyX3NoaWZ0ZWQzX3IgPC0gMjAqbG9nMTAoTW9kKGZzaGlmdDNfcikpDQoNCiMgZmllbGRzOjppbWFnZS5wbG90KHNwZWN0cl9zaGlmdGVkM19yKQ0KDQojIENBTkFMIFZFUkRFDQoNCnNtYWxsM19nIDwtIDEwDQoNCmZzaGlmdDNfZ1swOjYwMDAsIDA6MTIwMF0gPC0gc21hbGwzX2cNCmZzaGlmdDNfZ1swOjYwMDAsIDI4MDA6NDAwMF0gPC0gc21hbGwzX2cNCmZzaGlmdDNfZ1swOjI1MDAsIDEyMDA6MjgwMF0gPC0gc21hbGwzX2cNCmZzaGlmdDNfZ1szNTAwOjYwMDAsIDEyMDA6MjgwMF0gPC0gc21hbGwzX2cNCg0Kc3BlY3RyX3NoaWZ0ZWQzX2cgPC0gMjAqbG9nMTAoTW9kKGZzaGlmdDNfZykpDQoNCiMgZmllbGRzOjppbWFnZS5wbG90KHNwZWN0cl9zaGlmdGVkM19nKQ0KDQojIENBTkFMIEFaVUwNCg0Kc21hbGwzX2IgPC0gMTANCg0KZnNoaWZ0M19iWzA6NjAwMCwgMDoxMjAwXSA8LSBzbWFsbDNfYg0KZnNoaWZ0M19iWzA6NjAwMCwgMjgwMDo0MDAwXSA8LSBzbWFsbDNfYg0KZnNoaWZ0M19iWzA6MjUwMCwgMTIwMDoyODAwXSA8LSBzbWFsbDNfYg0KZnNoaWZ0M19iWzM1MDA6NjAwMCwgMTIwMDoyODAwXSA8LSBzbWFsbDNfYg0KDQpzcGVjdHJfc2hpZnRlZDNfYiA8LSAyMCpsb2cxMChNb2QoZnNoaWZ0M19iKSkNCg0KIyBmaWVsZHM6OmltYWdlLnBsb3Qoc3BlY3RyX3NoaWZ0ZWQxX2IpDQoNCmBgYA0KDQpgYGB7ciBlY2hvPUZ9DQpmZnQzbW9kX3IgPC0gZ3NpZ25hbDo6aWZmdHNoaWZ0KGZzaGlmdDNfciwgTUFSR0lOID0gYygxLDIpKQ0KaW1nXzNfciA8LSBSZShmZnQoZmZ0M21vZF9yLCBpbnZlcnNlID0gVCkpIC8gcHJvZChkaW0oZmZ0M21vZF9yKSkNCmZmdDNtb2RfZyA8LSBnc2lnbmFsOjppZmZ0c2hpZnQoZnNoaWZ0M19nLCBNQVJHSU4gPSBjKDEsMikpDQppbWdfM19nIDwtIFJlKGZmdChmZnQzbW9kX2csIGludmVyc2UgPSBUKSkgLyBwcm9kKGRpbShmZnQzbW9kX2cpKQ0KZmZ0M21vZF9iIDwtIGdzaWduYWw6OmlmZnRzaGlmdChmc2hpZnQzX2IsIE1BUkdJTiA9IGMoMSwyKSkNCmltZ18zX2IgPC0gUmUoZmZ0KGZmdDNtb2RfYiwgaW52ZXJzZSA9IFQpKSAvIHByb2QoZGltKGZmdDNtb2RfYikpDQoNCmltZ18zIDwtIGFycmF5KDAsIGRpbSA9IGRpbShpbWdyXzMpKQ0KaW1nXzNbLCwxXSA8LSBpbWdfM19yDQppbWdfM1ssLDJdIDwtIGltZ18zX2cNCmltZ18zWywsM10gPC0gaW1nXzNfYg0KDQpgYGANCg0KDQpgYGB7ciBlY2hvPUZ9DQpwbG90KEltYWdlKChpbWdyXzMpLCBjb2xvcm1vZGUgPSAiQ29sb3IiKSkNCnBsb3QoSW1hZ2UoKGltZ18zKSwgY29sb3Jtb2RlID0gIkNvbG9yIikpDQpgYGANCg0KIyMjIEltYWdlbiA0IHkgcnVpZG8gc2FsIHkgcGltaWVudGENCg0KDQpgYGB7ciBlY2hvPUZ9DQojIENBTkFMIFJPSk8NCg0KZmZ0NF9yIDwtIGZmdChpbWdyXzRbLCwxXSkNCm40X3IgPC0gbGVuZ3RoKGZmdDRfcikNCg0KZnNoaWZ0NF9yIDwtIGdzaWduYWw6OmZmdHNoaWZ0KGZmdDRfciwgTUFSR0lOID0gYygxLDIpKQ0Kc3BlY3RyNF9yIDwtIDIwKmxvZzEwKE1vZChmc2hpZnQ0X3IpKQ0KDQojIGZpZWxkczo6aW1hZ2UucGxvdChzcGVjdHI0X3IpDQoNCiMgQ0FOQUwgVkVSREUNCg0KZmZ0NF9nIDwtIGZmdChpbWdyXzRbLCwyXSkNCm40X2cgPC0gbGVuZ3RoKGZmdDRfZykNCg0KZnNoaWZ0NF9nIDwtIGdzaWduYWw6OmZmdHNoaWZ0KGZmdDRfZywgTUFSR0lOID0gYygxLDIpKQ0Kc3BlY3RyNF9nIDwtIDIwKmxvZzEwKE1vZChmc2hpZnQ0X2cpKQ0KDQojIGZpZWxkczo6aW1hZ2UucGxvdChzcGVjdHI0X2cpDQoNCiMgQ0FOQUwgQVpVTA0KDQpmZnQ0X2IgPC0gZmZ0KGltZ3JfNFssLDNdKQ0KbjRfYiA8LSBsZW5ndGgoZmZ0NF9iKQ0KDQpmc2hpZnQ0X2IgPC0gZ3NpZ25hbDo6ZmZ0c2hpZnQoZmZ0NF9iLCBNQVJHSU4gPSBjKDEsMikpDQpzcGVjdHI0X2IgPC0gMjAqbG9nMTAoTW9kKGZzaGlmdDRfYikpDQoNCiMgZmllbGRzOjppbWFnZS5wbG90KHNwZWN0cjRfYikNCmBgYA0KDQpgYGB7ciBlY2hvPUZ9DQoNCiMgQ0FOQUwgUk9KTw0KDQpzbWFsbDRfciA8LSAxMA0KDQpmc2hpZnQ0X3JbMDo2MDAwLCAwOjEyMDBdIDwtIHNtYWxsNF9yDQpmc2hpZnQ0X3JbMDo2MDAwLCAyODAwOjQwMDBdIDwtIHNtYWxsNF9yDQpmc2hpZnQ0X3JbMDoyNTAwLCAxMjAwOjI4MDBdIDwtIHNtYWxsNF9yDQpmc2hpZnQ0X3JbMzUwMDo2MDAwLCAxMjAwOjI4MDBdIDwtIHNtYWxsNF9yDQoNCnNwZWN0cl9zaGlmdGVkNF9yIDwtIDIwKmxvZzEwKE1vZChmc2hpZnQ0X3IpKQ0KDQojIGZpZWxkczo6aW1hZ2UucGxvdChzcGVjdHJfc2hpZnRlZDRfcikNCg0KIyBDQU5BTCBWRVJERQ0KDQpzbWFsbDRfZyA8LSAxMA0KDQpmc2hpZnQ0X2dbMDo2MDAwLCAwOjEyMDBdIDwtIHNtYWxsNF9nDQpmc2hpZnQ0X2dbMDo2MDAwLCAyODAwOjQwMDBdIDwtIHNtYWxsNF9nDQpmc2hpZnQ0X2dbMDoyNTAwLCAxMjAwOjI4MDBdIDwtIHNtYWxsNF9nDQpmc2hpZnQ0X2dbMzUwMDo2MDAwLCAxMjAwOjI4MDBdIDwtIHNtYWxsNF9nDQoNCnNwZWN0cl9zaGlmdGVkNF9nIDwtIDIwKmxvZzEwKE1vZChmc2hpZnQ0X2cpKQ0KDQojIGZpZWxkczo6aW1hZ2UucGxvdChzcGVjdHJfc2hpZnRlZDRfZykNCg0KIyBDQU5BTCBBWlVMDQoNCnNtYWxsNF9iIDwtIDEwDQoNCmZzaGlmdDRfYlswOjYwMDAsIDA6MTIwMF0gPC0gc21hbGw0X2INCmZzaGlmdDRfYlswOjYwMDAsIDI4MDA6NDAwMF0gPC0gc21hbGw0X2INCmZzaGlmdDRfYlswOjI1MDAsIDEyMDA6MjgwMF0gPC0gc21hbGw0X2INCmZzaGlmdDRfYlszNTAwOjYwMDAsIDEyMDA6MjgwMF0gPC0gc21hbGw0X2INCg0Kc3BlY3RyX3NoaWZ0ZWQ0X2IgPC0gMjAqbG9nMTAoTW9kKGZzaGlmdDRfYikpDQoNCiMgZmllbGRzOjppbWFnZS5wbG90KHNwZWN0cl9zaGlmdGVkNF9iKQ0KDQpgYGANCg0KYGBge3IgZWNobz1GfQ0KZmZ0NG1vZF9yIDwtIGdzaWduYWw6OmlmZnRzaGlmdChmc2hpZnQ0X3IsIE1BUkdJTiA9IGMoMSwyKSkNCmltZ180X3IgPC0gUmUoZmZ0KGZmdDRtb2RfciwgaW52ZXJzZSA9IFQpKSAvIHByb2QoZGltKGZmdDRtb2RfcikpDQpmZnQ0bW9kX2cgPC0gZ3NpZ25hbDo6aWZmdHNoaWZ0KGZzaGlmdDRfZywgTUFSR0lOID0gYygxLDIpKQ0KaW1nXzRfZyA8LSBSZShmZnQoZmZ0NG1vZF9nLCBpbnZlcnNlID0gVCkpIC8gcHJvZChkaW0oZmZ0NG1vZF9nKSkNCmZmdDRtb2RfYiA8LSBnc2lnbmFsOjppZmZ0c2hpZnQoZnNoaWZ0NF9iLCBNQVJHSU4gPSBjKDEsMikpDQppbWdfNF9iIDwtIFJlKGZmdChmZnQ0bW9kX2IsIGludmVyc2UgPSBUKSkgLyBwcm9kKGRpbShmZnQ0bW9kX2IpKQ0KDQppbWdfNCA8LSBhcnJheSgwLCBkaW0gPSBkaW0oaW1ncl80KSkNCmltZ180WywsMV0gPC0gaW1nXzRfcg0KaW1nXzRbLCwyXSA8LSBpbWdfNF9nDQppbWdfNFssLDNdIDwtIGltZ180X2INCg0KYGBgDQoNCg0KYGBge3IgZWNobz1GfQ0KcGxvdChJbWFnZSgoaW1ncl80KSwgY29sb3Jtb2RlID0gIkNvbG9yIikpDQpwbG90KEltYWdlKChpbWdfNCksIGNvbG9ybW9kZSA9ICJDb2xvciIpKQ0KYGBgDQoNCiMjIyBJbWFnZW4gNSB5IHJ1aWRvIGdhbW1hDQoNCg0KYGBge3IgZWNobz1GfQ0KIyBDQU5BTCBST0pPDQoNCmZmdDVfciA8LSBmZnQoaW1ncl81WywsMV0pDQpuNV9yIDwtIGxlbmd0aChmZnQ1X3IpDQoNCmZzaGlmdDVfciA8LSBnc2lnbmFsOjpmZnRzaGlmdChmZnQ1X3IsIE1BUkdJTiA9IGMoMSwyKSkNCnNwZWN0cjVfciA8LSAyMCpsb2cxMChNb2QoZnNoaWZ0NV9yKSkNCg0KIyBmaWVsZHM6OmltYWdlLnBsb3Qoc3BlY3RyNV9yKQ0KDQojIENBTkFMIFZFUkRFDQoNCmZmdDVfZyA8LSBmZnQoaW1ncl81WywsMl0pDQpuNV9nIDwtIGxlbmd0aChmZnQ1X2cpDQoNCmZzaGlmdDVfZyA8LSBnc2lnbmFsOjpmZnRzaGlmdChmZnQ1X2csIE1BUkdJTiA9IGMoMSwyKSkNCnNwZWN0cjVfZyA8LSAyMCpsb2cxMChNb2QoZnNoaWZ0NV9nKSkNCg0KIyBmaWVsZHM6OmltYWdlLnBsb3Qoc3BlY3RyNV9nKQ0KDQojIENBTkFMIEFaVUwNCg0KZmZ0NV9iIDwtIGZmdChpbWdyXzVbLCwzXSkNCm41X2IgPC0gbGVuZ3RoKGZmdDVfYikNCg0KZnNoaWZ0NV9iIDwtIGdzaWduYWw6OmZmdHNoaWZ0KGZmdDVfYiwgTUFSR0lOID0gYygxLDIpKQ0Kc3BlY3RyNV9iIDwtIDIwKmxvZzEwKE1vZChmc2hpZnQ1X2IpKQ0KDQojIGZpZWxkczo6aW1hZ2UucGxvdChzcGVjdHI1X2IpDQpgYGANCg0KYGBge3IgZWNobz1GfQ0KDQojIENBTkFMIFJPSk8NCg0Kc21hbGw1X3IgPC0gMTANCg0KZnNoaWZ0NV9yWzA6MTYwMCwgMDo0NTBdIDwtIHNtYWxsNV9yDQpmc2hpZnQ1X3JbMDoxNjAwLCA2MTY6MTA2Nl0gPC0gc21hbGw1X3INCmZzaGlmdDVfclswOjY1MCwgNDUwOjYxNl0gPC0gc21hbGw1X3INCmZzaGlmdDVfcls5NTA6MTYwMCwgNDUwOjYxNl0gPC0gc21hbGw1X3INCg0Kc3BlY3RyX3NoaWZ0ZWQxX3IgPC0gMjAqbG9nMTAoTW9kKGZzaGlmdDVfcikpDQoNCiMgZmllbGRzOjppbWFnZS5wbG90KHNwZWN0cl9zaGlmdGVkNV9yKQ0KDQojIENBTkFMIFZFUkRFDQoNCnNtYWxsNV9nIDwtIDEwDQoNCmZzaGlmdDVfZ1swOjE2MDAsIDA6NDUwXSA8LSBzbWFsbDVfZw0KZnNoaWZ0NV9nWzA6MTYwMCwgNjE2OjEwNjZdIDwtIHNtYWxsNV9nDQpmc2hpZnQ1X2dbMDo2NTAsIDQ1MDo2MTZdIDwtIHNtYWxsNV9nDQpmc2hpZnQ1X2dbOTUwOjE2MDAsIDQ1MDo2MTZdIDwtIHNtYWxsNV9nDQoNCnNwZWN0cl9zaGlmdGVkNV9nIDwtIDIwKmxvZzEwKE1vZChmc2hpZnQ1X2cpKQ0KDQojIGZpZWxkczo6aW1hZ2UucGxvdChzcGVjdHJfc2hpZnRlZDVfZykNCg0KIyBDQU5BTCBBWlVMDQoNCnNtYWxsNV9iIDwtIDEwDQoNCmZzaGlmdDVfYlswOjE2MDAsIDA6NDUwXSA8LSBzbWFsbDVfYg0KZnNoaWZ0NV9iWzA6MTYwMCwgNjE2OjEwNjZdIDwtIHNtYWxsNV9iDQpmc2hpZnQ1X2JbMDo2NTAsIDQ1MDo2MTZdIDwtIHNtYWxsNV9iDQpmc2hpZnQ1X2JbOTUwOjE2MDAsIDQ1MDo2MTZdIDwtIHNtYWxsNV9iDQoNCnNwZWN0cl9zaGlmdGVkNV9iIDwtIDIwKmxvZzEwKE1vZChmc2hpZnQ1X2IpKQ0KDQojIGZpZWxkczo6aW1hZ2UucGxvdChzcGVjdHJfc2hpZnRlZDVfYikNCg0KYGBgDQoNCmBgYHtyIGVjaG89Rn0NCmZmdDVtb2RfciA8LSBnc2lnbmFsOjppZmZ0c2hpZnQoZnNoaWZ0NV9yLCBNQVJHSU4gPSBjKDEsMikpDQppbWdfNV9yIDwtIFJlKGZmdChmZnQ1bW9kX3IsIGludmVyc2UgPSBUKSkgLyBwcm9kKGRpbShmZnQ1bW9kX3IpKQ0KZmZ0NW1vZF9nIDwtIGdzaWduYWw6OmlmZnRzaGlmdChmc2hpZnQ1X2csIE1BUkdJTiA9IGMoMSwyKSkNCmltZ181X2cgPC0gUmUoZmZ0KGZmdDVtb2RfZywgaW52ZXJzZSA9IFQpKSAvIHByb2QoZGltKGZmdDVtb2RfZykpDQpmZnQ1bW9kX2IgPC0gZ3NpZ25hbDo6aWZmdHNoaWZ0KGZzaGlmdDVfYiwgTUFSR0lOID0gYygxLDIpKQ0KaW1nXzVfYiA8LSBSZShmZnQoZmZ0NW1vZF9iLCBpbnZlcnNlID0gVCkpIC8gcHJvZChkaW0oZmZ0NW1vZF9iKSkNCg0KaW1nXzUgPC0gYXJyYXkoMCwgZGltID0gZGltKGltZ3JfNSkpDQppbWdfNVssLDFdIDwtIGltZ181X3INCmltZ181WywsMl0gPC0gaW1nXzVfZw0KaW1nXzVbLCwzXSA8LSBpbWdfNV9iDQoNCmBgYA0KDQoNCmBgYHtyIGVjaG89Rn0NCnBsb3QoSW1hZ2UoKGltZ3JfNSksIGNvbG9ybW9kZSA9ICJDb2xvciIpKQ0KcGxvdChJbWFnZSgoaW1nXzUpLCBjb2xvcm1vZGUgPSAiQ29sb3IiKSkNCmBgYA0KDQojIyMgSW1hZ2VuIDUgeSBydWlkbyBtdWx0aXBsaWNhdGl2byB1bmlmb3JtZQ0KDQoNCmBgYHtyIGVjaG89Rn0NCiMgQ0FOQUwgUk9KTw0KDQpmZnQ2X3IgPC0gZmZ0KGltZ3JfNlssLDFdKQ0KbjZfciA8LSBsZW5ndGgoZmZ0Nl9yKQ0KDQpmc2hpZnQ2X3IgPC0gZ3NpZ25hbDo6ZmZ0c2hpZnQoZmZ0Nl9yLCBNQVJHSU4gPSBjKDEsMikpDQpzcGVjdHI2X3IgPC0gMjAqbG9nMTAoTW9kKGZzaGlmdDZfcikpDQoNCiMgZmllbGRzOjppbWFnZS5wbG90KHNwZWN0cjZfcikNCg0KIyBDQU5BTCBWRVJERQ0KDQpmZnQ2X2cgPC0gZmZ0KGltZ3JfNlssLDJdKQ0KbjZfZyA8LSBsZW5ndGgoZmZ0Nl9nKQ0KDQpmc2hpZnQ2X2cgPC0gZ3NpZ25hbDo6ZmZ0c2hpZnQoZmZ0Nl9nLCBNQVJHSU4gPSBjKDEsMikpDQpzcGVjdHI2X2cgPC0gMjAqbG9nMTAoTW9kKGZzaGlmdDZfZykpDQoNCiMgZmllbGRzOjppbWFnZS5wbG90KHNwZWN0cjZfZykNCg0KIyBDQU5BTCBBWlVMDQoNCmZmdDZfYiA8LSBmZnQoaW1ncl82WywsM10pDQpuNl9iIDwtIGxlbmd0aChmZnQ2X2IpDQoNCmZzaGlmdDZfYiA8LSBnc2lnbmFsOjpmZnRzaGlmdChmZnQ2X2IsIE1BUkdJTiA9IGMoMSwyKSkNCnNwZWN0cjZfYiA8LSAyMCpsb2cxMChNb2QoZnNoaWZ0Nl9iKSkNCg0KIyBmaWVsZHM6OmltYWdlLnBsb3Qoc3BlY3RyNl9iKQ0KYGBgDQoNCmBgYHtyIGVjaG89Rn0NCg0KIyBDQU5BTCBST0pPDQoNCnNtYWxsNl9yIDwtIDE1DQoNCmZzaGlmdDZfclswOjE2MDAsIDA6NDUwXSA8LSBzbWFsbDZfcg0KZnNoaWZ0Nl9yWzA6MTYwMCwgNjE2OjEwNjZdIDwtIHNtYWxsNl9yDQpmc2hpZnQ2X3JbMDo2NTAsIDQ1MDo2MTZdIDwtIHNtYWxsNl9yDQpmc2hpZnQ2X3JbOTUwOjE2MDAsIDQ1MDo2MTZdIDwtIHNtYWxsNl9yDQoNCnNwZWN0cl9zaGlmdGVkNl9yIDwtIDIwKmxvZzEwKE1vZChmc2hpZnQ2X3IpKQ0KDQojIGZpZWxkczo6aW1hZ2UucGxvdChzcGVjdHJfc2hpZnRlZDZfcikNCg0KIyBDQU5BTCBWRVJERQ0KDQpzbWFsbDZfZyA8LSAxNQ0KDQpmc2hpZnQ2X2dbMDoxNjAwLCAwOjQ1MF0gPC0gc21hbGw2X2cNCmZzaGlmdDZfZ1swOjE2MDAsIDYxNjoxMDY2XSA8LSBzbWFsbDZfZw0KZnNoaWZ0Nl9nWzA6NjUwLCA0NTA6NjE2XSA8LSBzbWFsbDZfZw0KZnNoaWZ0Nl9nWzk1MDoxNjAwLCA0NTA6NjE2XSA8LSBzbWFsbDZfZw0KDQpzcGVjdHJfc2hpZnRlZDZfZyA8LSAyMCpsb2cxMChNb2QoZnNoaWZ0Nl9nKSkNCg0KIyBmaWVsZHM6OmltYWdlLnBsb3Qoc3BlY3RyX3NoaWZ0ZWQ2X2cpDQoNCiMgQ0FOQUwgQVpVTA0KDQpzbWFsbDZfYiA8LSAxNQ0KDQpmc2hpZnQ2X2JbMDoxNjAwLCAwOjQ1MF0gPC0gc21hbGw2X2INCmZzaGlmdDZfYlswOjE2MDAsIDYxNjoxMDY2XSA8LSBzbWFsbDZfYg0KZnNoaWZ0Nl9iWzA6NjUwLCA0NTA6NjE2XSA8LSBzbWFsbDZfYg0KZnNoaWZ0Nl9iWzk1MDoxNjAwLCA0NTA6NjE2XSA8LSBzbWFsbDZfYg0KDQpzcGVjdHJfc2hpZnRlZDZfYiA8LSAyMCpsb2cxMChNb2QoZnNoaWZ0Nl9iKSkNCg0KIyBmaWVsZHM6OmltYWdlLnBsb3Qoc3BlY3RyX3NoaWZ0ZWQ2X2IpDQoNCmBgYA0KDQpgYGB7ciBlY2hvPUZ9DQpmZnQ2bW9kX3IgPC0gZ3NpZ25hbDo6aWZmdHNoaWZ0KGZzaGlmdDZfciwgTUFSR0lOID0gYygxLDIpKQ0KaW1nXzZfciA8LSBSZShmZnQoZmZ0Nm1vZF9yLCBpbnZlcnNlID0gVCkpIC8gcHJvZChkaW0oZmZ0Nm1vZF9yKSkNCmZmdDZtb2RfZyA8LSBnc2lnbmFsOjppZmZ0c2hpZnQoZnNoaWZ0Nl9nLCBNQVJHSU4gPSBjKDEsMikpDQppbWdfNl9nIDwtIFJlKGZmdChmZnQ2bW9kX2csIGludmVyc2UgPSBUKSkgLyBwcm9kKGRpbShmZnQ2bW9kX2cpKQ0KZmZ0Nm1vZF9iIDwtIGdzaWduYWw6OmlmZnRzaGlmdChmc2hpZnQ2X2IsIE1BUkdJTiA9IGMoMSwyKSkNCmltZ182X2IgPC0gUmUoZmZ0KGZmdDZtb2RfYiwgaW52ZXJzZSA9IFQpKSAvIHByb2QoZGltKGZmdDZtb2RfYikpDQoNCmltZ182IDwtIGFycmF5KDAsIGRpbSA9IGRpbShpbWdyXzYpKQ0KaW1nXzZbLCwxXSA8LSBpbWdfNl9yDQppbWdfNlssLDJdIDwtIGltZ182X2cNCmltZ182WywsM10gPC0gaW1nXzZfYg0KDQpgYGANCg0KDQpgYGB7ciBlY2hvPUZ9DQpwbG90KEltYWdlKChpbWdyXzYpLCBjb2xvcm1vZGUgPSAiQ29sb3IiKSkNCnBsb3QoSW1hZ2UoKGltZ182KSwgY29sb3Jtb2RlID0gIkNvbG9yIikpDQpgYGANCiMgQ29uY2x1c2lvbmVzDQo=